Merge pull request #9533 from Budibase/develop

Develop -> master
This commit is contained in:
Martin McKeaveney 2023-02-05 23:47:56 +00:00 committed by GitHub
commit 3ce4bdd193
834 changed files with 22245 additions and 20850 deletions

View File

@ -6,6 +6,8 @@ labels: bug
assignees: '' assignees: ''
--- ---
**Checklist**
- [ ] I have searched budibase discussions and github issues to check if my issue already exists
**Hosting** **Hosting**
<!-- Delete as appropriate --> <!-- Delete as appropriate -->

View File

@ -26,7 +26,7 @@ env:
FEATURE_PREVIEW_URL: https://budirelease.live FEATURE_PREVIEW_URL: https://budirelease.live
jobs: jobs:
release: release-images:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -50,13 +50,6 @@ jobs:
- run: yarn build:sdk - run: yarn build:sdk
- run: yarn test - run: yarn test
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-1
- name: Publish budibase packages to NPM - name: Publish budibase packages to NPM
env: env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@ -152,3 +145,54 @@ jobs:
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env." content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env."
embed-title: ${{ env.RELEASE_VERSION }} embed-title: ${{ env.RELEASE_VERSION }}
release-helm-chart:
needs: [release-images]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Helm
uses: azure/setup-helm@v1
id: helm-install
# due to helm repo index issue: https://github.com/helm/helm/issues/7363
# we need to create new package in a different dir, merge the index and move the package back
- name: Build and release helm chart
run: |
git config user.name "Budibase Helm Bot"
git config user.email "<>"
git reset --hard
git pull
mkdir sync
echo "Packaging chart to sync dir"
helm package charts/budibase --version 0.0.0-develop --app-version develop --destination sync
echo "Packaging successful"
git checkout gh-pages
echo "Indexing helm repo"
helm repo index --merge docs/index.yaml sync
mv -f sync/* docs
rm -rf sync
echo "Pushing new helm release"
git add -A
git commit -m "Helm Release: develop"
git push
trigger-deploy-to-qa-env:
needs: [release-helm-chart]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Get the current budibase release version
id: version
run: |
release_version=$(cat lerna.json | jq -r '.version')
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
- uses: passeidireto/trigger-external-workflow-action@main
env:
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
with:
repository: budibase/budibase-deploys
event: deploy-budibase-develop-to-qa
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}

View File

@ -67,16 +67,24 @@ jobs:
uses: azure/setup-helm@v1 uses: azure/setup-helm@v1
id: helm-install id: helm-install
# due to helm repo index issue: https://github.com/helm/helm/issues/7363
# we need to create new package in a different dir, merge the index and move the package back
- name: Build and release helm chart - name: Build and release helm chart
run: | run: |
git config user.name "Budibase Helm Bot" git config user.name "Budibase Helm Bot"
git config user.email "<>" git config user.email "<>"
git reset --hard git reset --hard
git pull git pull
helm package charts/budibase mkdir sync
echo "Packaging chart to sync dir"
helm package charts/budibase --version "$RELEASE_VERSION" --app-version "$RELEASE_VERSION" --destination sync
echo "Packaging successful"
git checkout gh-pages git checkout gh-pages
mv *.tgz docs echo "Indexing helm repo"
helm repo index docs helm repo index --merge docs/index.yaml sync
mv -f sync/* docs
rm -rf sync
echo "Pushing new helm release"
git add -A git add -A
git commit -m "Helm Release: ${{ env.RELEASE_VERSION }}" git commit -m "Helm Release: ${{ env.RELEASE_VERSION }}"
git push git push

View File

@ -18,30 +18,18 @@ jobs:
- run: yarn - run: yarn
- run: yarn bootstrap - run: yarn bootstrap
- run: yarn build - run: yarn build
- name: Pull cypress.env.yaml from budibase-infra - name: Pull from budibase-infra
run: | run: |
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
-H 'Accept: application/vnd.github.v3.raw' \ -H 'Accept: application/vnd.github.v3.raw' \
-o packages/builder/cypress.env.json \ -o
-L https://api.github.com/repos/budibase/budibase-infra/contents/test/cypress.env.json -L
wc -l packages/builder/cypress.env.json wc -l
- name: Cypress run
id: cypress
continue-on-error: true
uses: cypress-io/github-action@v2
with:
record: true
install: false
tag: nightly
command: yarn test:e2e:ci:record
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: Test Reports name: Test Reports
path: packages/builder/cypress/reports/testReport.html path:
# TODO: enable once running in QA test env # TODO: enable once running in QA test env
# - name: Configure AWS Credentials # - name: Configure AWS Credentials
@ -54,11 +42,3 @@ jobs:
# - name: Upload test results HTML # - name: Upload test results HTML
# uses: aws-actions/configure-aws-credentials@v1 # uses: aws-actions/configure-aws-credentials@v1
# run: aws s3 cp packages/builder/cypress/reports/testReport.html s3://{{ secrets.BUDI_QA_REPORTS_BUCKET_NAME }}/$GITHUB_RUN_ID/index.html # run: aws s3 cp packages/builder/cypress/reports/testReport.html s3://{{ secrets.BUDI_QA_REPORTS_BUCKET_NAME }}/$GITHUB_RUN_ID/index.html
- name: Cypress Discord Notify
run: yarn test:e2e:ci:notify
env:
CYPRESS_WEBHOOK_URL: ${{ secrets.BUDI_QA_WEBHOOK }}
CYPRESS_OUTCOME: ${{ steps.cypress.outcome }}
CYPRESS_DASHBOARD_URL: ${{ steps.cypress.outputs.dashboardUrl }}
GITHUB_RUN_URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID

2
.gitignore vendored
View File

@ -107,3 +107,5 @@ stats.html
# plugins # plugins
budibase-component budibase-component
budibase-datasource budibase-datasource
*.iml

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v14.19.3

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.11.1

2
.tool-versions Normal file
View File

@ -0,0 +1,2 @@
nodejs 14.19.3
python 3.11.1

6
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"svelte.svelte-vscode"
]
}

43
.vscode/settings.json vendored
View File

@ -1,19 +1,28 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": true "source.fixAll": true
}, },
"editor.defaultFormatter": "svelte.svelte-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"[json]": { "[json]": {
"editor.defaultFormatter": "vscode.json-language-features" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[javascript]": { "[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"debug.javascript.terminalOptions": { "debug.javascript.terminalOptions": {
"skipFiles": [ "skipFiles": [
"${workspaceFolder}/packages/backend-core/node_modules/**", "${workspaceFolder}/packages/backend-core/node_modules/**",
"<node_internals>/**" "<node_internals>/**"
] ]
}, },
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[dockercompose]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode"
}
} }

View File

@ -11,8 +11,10 @@ sources:
- https://github.com/Budibase/budibase - https://github.com/Budibase/budibase
- https://budibase.com - https://budibase.com
type: application type: application
version: 0.2.11 # populates on packaging
appVersion: 1.0.214 version: 0.0.0
# populates on packaging
appVersion: 0.0.0
dependencies: dependencies:
- name: couchdb - name: couchdb
version: 3.3.4 version: 3.3.4

View File

@ -4,9 +4,6 @@ metadata:
annotations: annotations:
kompose.cmd: kompose convert kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8) kompose.version: 1.21.0 (992df58d8)
{{ if .Values.globals.logAnnotations }}
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
{{ end }}
creationTimestamp: null creationTimestamp: null
labels: labels:
io.kompose.service: app-service io.kompose.service: app-service
@ -23,6 +20,9 @@ spec:
annotations: annotations:
kompose.cmd: kompose convert kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8) kompose.version: 1.21.0 (992df58d8)
{{ if .Values.services.apps.annotations }}
{{- toYaml .Values.services.apps.annotations | indent 8 -}}
{{ end }}
creationTimestamp: null creationTimestamp: null
labels: labels:
io.kompose.service: app-service io.kompose.service: app-service
@ -67,6 +67,8 @@ spec:
- name: AWS_REGION - name: AWS_REGION
value: {{ .Values.services.objectStore.region }} value: {{ .Values.services.objectStore.region }}
{{ end }} {{ end }}
- name: MINIO_ENABLED
value: {{ .Values.services.objectStore.minio | quote }}
- name: MINIO_ACCESS_KEY - name: MINIO_ACCESS_KEY
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@ -77,13 +79,19 @@ spec:
secretKeyRef: secretKeyRef:
name: {{ template "budibase.fullname" . }} name: {{ template "budibase.fullname" . }}
key: objectStoreSecret key: objectStoreSecret
- name: CLOUDFRONT_CDN
value: {{ .Values.services.objectStore.cloudfront.cdn | quote }}
- name: CLOUDFRONT_PUBLIC_KEY_ID
value: {{ .Values.services.objectStore.cloudfront.publicKeyId | quote }}
- name: CLOUDFRONT_PRIVATE_KEY_64
value: {{ .Values.services.objectStore.cloudfront.privateKey64 | quote }}
- name: MINIO_URL - name: MINIO_URL
value: {{ .Values.services.objectStore.url }} value: {{ .Values.services.objectStore.url }}
- name: PLUGIN_BUCKET_NAME - name: PLUGIN_BUCKET_NAME
value: {{ .Values.services.objectStore.pluginBucketName | quote }} value: {{ .Values.services.objectStore.pluginBucketName | quote }}
- name: APPS_BUCKET_NAME - name: APPS_BUCKET_NAME
value: {{ .Values.services.objectStore.appsBucketName | quote }} value: {{ .Values.services.objectStore.appsBucketName | quote }}
- name: GLOBAL_CLOUD_BUCKET_NAME - name: GLOBAL_BUCKET_NAME
value: {{ .Values.services.objectStore.globalBucketName | quote }} value: {{ .Values.services.objectStore.globalBucketName | quote }}
- name: BACKUPS_BUCKET_NAME - name: BACKUPS_BUCKET_NAME
value: {{ .Values.services.objectStore.backupsBucketName | quote }} value: {{ .Values.services.objectStore.backupsBucketName | quote }}
@ -131,6 +139,8 @@ spec:
value: {{ .Values.globals.automationMaxIterations | quote }} value: {{ .Values.globals.automationMaxIterations | quote }}
- name: TENANT_FEATURE_FLAGS - name: TENANT_FEATURE_FLAGS
value: {{ .Values.globals.tenantFeatureFlags | quote }} value: {{ .Values.globals.tenantFeatureFlags | quote }}
- name: ENCRYPTION_KEY
value: {{ .Values.globals.bbEncryptionKey | quote }}
{{ if .Values.globals.bbAdminUserEmail }} {{ if .Values.globals.bbAdminUserEmail }}
- name: BB_ADMIN_USER_EMAIL - name: BB_ADMIN_USER_EMAIL
value: {{ .Values.globals.bbAdminUserEmail | quote }} value: {{ .Values.globals.bbAdminUserEmail | quote }}

View File

@ -4,9 +4,6 @@ metadata:
annotations: annotations:
kompose.cmd: kompose convert kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8) kompose.version: 1.21.0 (992df58d8)
{{ if .Values.globals.logAnnotations }}
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
{{ end }}
creationTimestamp: null creationTimestamp: null
labels: labels:
app.kubernetes.io/name: budibase-proxy app.kubernetes.io/name: budibase-proxy
@ -23,6 +20,9 @@ spec:
annotations: annotations:
kompose.cmd: kompose convert kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8) kompose.version: 1.21.0 (992df58d8)
{{ if .Values.services.proxy.annotations }}
{{- toYaml .Values.services.proxy.annotations | indent 8 -}}
{{ end }}
creationTimestamp: null creationTimestamp: null
labels: labels:
app.kubernetes.io/name: budibase-proxy app.kubernetes.io/name: budibase-proxy

View File

@ -4,9 +4,6 @@ metadata:
annotations: annotations:
kompose.cmd: kompose convert kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8) kompose.version: 1.21.0 (992df58d8)
{{ if .Values.globals.logAnnotations }}
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
{{ end }}
creationTimestamp: null creationTimestamp: null
labels: labels:
io.kompose.service: worker-service io.kompose.service: worker-service
@ -24,6 +21,9 @@ spec:
annotations: annotations:
kompose.cmd: kompose convert kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8) kompose.version: 1.21.0 (992df58d8)
{{ if .Values.services.worker.annotations }}
{{- toYaml .Values.services.worker.annotations | indent 8 -}}
{{ end }}
creationTimestamp: null creationTimestamp: null
labels: labels:
io.kompose.service: worker-service io.kompose.service: worker-service
@ -68,6 +68,8 @@ spec:
- name: AWS_REGION - name: AWS_REGION
value: {{ .Values.services.objectStore.region }} value: {{ .Values.services.objectStore.region }}
{{ end }} {{ end }}
- name: MINIO_ENABLED
value: {{ .Values.services.objectStore.minio | quote }}
- name: MINIO_ACCESS_KEY - name: MINIO_ACCESS_KEY
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@ -80,11 +82,17 @@ spec:
key: objectStoreSecret key: objectStoreSecret
- name: MINIO_URL - name: MINIO_URL
value: {{ .Values.services.objectStore.url }} value: {{ .Values.services.objectStore.url }}
- name: CLOUDFRONT_CDN
value: {{ .Values.services.objectStore.cloudfront.cdn | quote }}
- name: CLOUDFRONT_PUBLIC_KEY_ID
value: {{ .Values.services.objectStore.cloudfront.publicKeyId | quote }}
- name: CLOUDFRONT_PRIVATE_KEY_64
value: {{ .Values.services.objectStore.cloudfront.privateKey64 | quote }}
- name: PLUGIN_BUCKET_NAME - name: PLUGIN_BUCKET_NAME
value: {{ .Values.services.objectStore.pluginBucketName | quote }} value: {{ .Values.services.objectStore.pluginBucketName | quote }}
- name: APPS_BUCKET_NAME - name: APPS_BUCKET_NAME
value: {{ .Values.services.objectStore.appsBucketName | quote }} value: {{ .Values.services.objectStore.appsBucketName | quote }}
- name: GLOBAL_CLOUD_BUCKET_NAME - name: GLOBAL_BUCKET_NAME
value: {{ .Values.services.objectStore.globalBucketName | quote }} value: {{ .Values.services.objectStore.globalBucketName | quote }}
- name: BACKUPS_BUCKET_NAME - name: BACKUPS_BUCKET_NAME
value: {{ .Values.services.objectStore.backupsBucketName | quote }} value: {{ .Values.services.objectStore.backupsBucketName | quote }}
@ -138,6 +146,8 @@ spec:
value: {{ .Values.globals.google.secret | quote }} value: {{ .Values.globals.google.secret | quote }}
- name: TENANT_FEATURE_FLAGS - name: TENANT_FEATURE_FLAGS
value: {{ .Values.globals.tenantFeatureFlags | quote }} value: {{ .Values.globals.tenantFeatureFlags | quote }}
- name: ENCRYPTION_KEY
value: {{ .Values.globals.bbEncryptionKey | quote }}
{{ if .Values.globals.elasticApmEnabled }} {{ if .Values.globals.elasticApmEnabled }}
- name: ELASTIC_APM_ENABLED - name: ELASTIC_APM_ENABLED
value: {{ .Values.globals.elasticApmEnabled | quote }} value: {{ .Values.globals.elasticApmEnabled | quote }}

View File

@ -22,12 +22,6 @@ serviceAccount:
podAnnotations: {} podAnnotations: {}
# logAnnotations:
# co.elastic.logs/multiline.type: pattern
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
# co.elastic.logs/multiline.negate: false
# co.elastic.logs/multiline.match: after
podSecurityContext: podSecurityContext:
{} {}
# fsGroup: 2000 # fsGroup: 2000
@ -82,7 +76,7 @@ affinity: {}
globals: globals:
appVersion: "latest" appVersion: "latest"
budibaseEnv: PRODUCTION budibaseEnv: PRODUCTION
tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS" tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
enableAnalytics: "1" enableAnalytics: "1"
sentryDSN: "" sentryDSN: ""
posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU" posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU"
@ -130,6 +124,10 @@ services:
minio: 'http://minio-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.objectStore.port }}' minio: 'http://minio-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.objectStore.port }}'
couchdb: 'http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}' couchdb: 'http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}'
resources: {} resources: {}
# annotations:
# co.elastic.logs/module: nginx
# co.elastic.logs/fileset.stdout: access
# co.elastic.logs/fileset.stderr: error
apps: apps:
port: 4002 port: 4002
@ -137,11 +135,20 @@ services:
logLevel: info logLevel: info
resources: {} resources: {}
# nodeDebug: "" # set the value of NODE_DEBUG # nodeDebug: "" # set the value of NODE_DEBUG
# annotations:
# co.elastic.logs/multiline.type: pattern
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
# co.elastic.logs/multiline.negate: false
# co.elastic.logs/multiline.match: after
worker: worker:
port: 4003 port: 4003
replicaCount: 1 replicaCount: 1
resources: {} resources: {}
# annotations:
# co.elastic.logs/multiline.type: pattern
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
# co.elastic.logs/multiline.negate: false
# co.elastic.logs/multiline.match: after
couchdb: couchdb:
enabled: true enabled: true
@ -172,6 +179,7 @@ services:
resources: {} resources: {}
objectStore: objectStore:
# Set to false if using another object store such as S3
minio: true minio: true
browser: true browser: true
port: 9000 port: 9000
@ -187,6 +195,13 @@ services:
## set, choosing the default provisioner. ## set, choosing the default provisioner.
storageClass: "" storageClass: ""
resources: {} resources: {}
cloudfront:
# Set the url of a distribution to enable cloudfront
cdn: ""
# ID of public key stored in cloudfront
publicKeyId: ""
# Base64 encoded private key for the above public key
privateKey64: ""
# Override values in couchDB subchart # Override values in couchDB subchart
couchdb: couchdb:

View File

@ -9,7 +9,6 @@ From opening a bug report to creating a pull request: every contribution is appr
- [Glossary of Terms](#glossary-of-terms) - [Glossary of Terms](#glossary-of-terms)
- [Contributing to Budibase](#contributing-to-budibase) - [Contributing to Budibase](#contributing-to-budibase)
## Not Sure Where to Start? ## Not Sure Where to Start?
Budibase is a low-code web application builder that creates svelte-based web applications. Budibase is a low-code web application builder that creates svelte-based web applications.
@ -77,24 +76,51 @@ Component libraries are collections of components as well as the definition of t
## Contributing to Budibase ## Contributing to Budibase
* Please maintain the existing code style. - Please maintain the existing code style.
* Please try to keep your commits small and focused. - Please try to keep your commits small and focused.
* Please write tests. - Please write tests.
* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read. - If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read.
* Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why. - Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why.
### Getting Started For Contributors ### Getting Started For Contributors
#### 1. Prerequisites
NodeJS Version `14.x.x` #### 1. Prerequisites
*yarn -* `npm install -g yarn` - NodeJS version `14.x.x`
- Python version `3.x`
*jest* - `npm install -g jest` ### Using asdf (recommended)
Asdf is a package manager that allows managing multiple dependencies.
You can install them following any of the steps described below:
- Install using script (only for mac users):
`./scripts/install-contributor-dependencies.sh`
- Or, manually:
- Installation steps: https://asdf-vm.com/guide/getting-started.html
- asdf plugin add nodejs
- asdf plugin add python
- npm install -g yarn
### Using NVM and pyenv
- NVM:
- Install: https://github.com/nvm-sh/nvm#installing-and-updating
- Setup: `nvm use`
- Pyenv:
- Install: https://github.com/pyenv/pyenv#installation
- Setup: `pyenv install -v 3.7.2`
- _yarn -_ `npm install -g yarn`
#### 2. Clone this repository #### 2. Clone this repository
@ -102,7 +128,7 @@ NodeJS Version `14.x.x`
then `cd ` into your local copy. then `cd ` into your local copy.
#### 3. Install and Build #### 3. Install and Build
| **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash) | **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash)
@ -172,30 +198,38 @@ A combination of environment variables controls the mode budibase runs in.
Yarn commands can be used to mimic the different modes as described in the sections below: Yarn commands can be used to mimic the different modes as described in the sections below:
#### Self Hosted #### Self Hosted
The default mode. A single tenant installation with no usage restrictions. The default mode. A single tenant installation with no usage restrictions.
To enable this mode, use: To enable this mode, use:
``` ```
yarn mode:self yarn mode:self
``` ```
#### Cloud #### Cloud
The cloud mode, with account portal turned off. The cloud mode, with account portal turned off.
To enable this mode, use: To enable this mode, use:
``` ```
yarn mode:cloud yarn mode:cloud
``` ```
#### Cloud & Account #### Cloud & Account
The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app
To enable this mode, use: To enable this mode, use:
``` ```
yarn mode:account yarn mode:account
``` ```
### CI ### CI
An overview of the CI pipelines can be found [here](../.github/workflows/README.md)
An overview of the CI pipelines can be found [here](../.github/workflows/README.md)
### Pro ### Pro
@ -214,6 +248,7 @@ The `yarn bootstrap` command can be used to replace the NPM supplied dependency
### Troubleshooting ### Troubleshooting
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation. Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation.
### Running tests ### Running tests
#### End-to-end Tests #### End-to-end Tests
@ -226,12 +261,11 @@ yarn test:e2e
Or if you are in the builder you can run `yarn cy:test`. Or if you are in the builder you can run `yarn cy:test`.
### Other Useful Information ### Other Useful Information
* The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself). - The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself).
* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE). - This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE).
* We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions. - We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions.
Please read this if you are unfamiliar with it. Please read this if you are unfamiliar with it.

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,8 @@ services:
minio-service: minio-service:
container_name: budi-minio-dev container_name: budi-minio-dev
restart: on-failure restart: on-failure
image: minio/minio # Last version that supports the "fs" backend
image: minio/minio:RELEASE.2022-10-24T18-35-07Z
volumes: volumes:
- minio_data:/data - minio_data:/data
ports: ports:

View File

@ -186,6 +186,26 @@ http {
proxy_pass http://minio-service:9000; proxy_pass http://minio-service:9000;
} }
location /files/signed/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# IMPORTANT: Signed urls will inspect the host header of the request.
# Normally a signed url will need to be generated with a specified client host in mind.
# To support dynamic hosts, e.g. some unknown self-hosted installation url,
# use a predefined host header. The host 'minio-service' is also used at the time of url signing.
proxy_set_header Host minio-service;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://minio-service:9000;
rewrite ^/files/signed/(.*)$ /$1 break;
}
client_header_timeout 60; client_header_timeout 60;
client_body_timeout 60; client_body_timeout 60;
keepalive_timeout 60; keepalive_timeout 60;

View File

@ -202,6 +202,26 @@ http {
proxy_pass $minio; proxy_pass $minio;
} }
location /files/signed/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# IMPORTANT: Signed urls will inspect the host header of the request.
# Normally a signed url will need to be generated with a specified client host in mind.
# To support dynamic hosts, e.g. some unknown self-hosted installation url,
# use a predefined host header. The host 'minio-service' is also used at the time of url signing.
proxy_set_header Host minio-service;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass $minio;
rewrite ^/files/signed/(.*)$ /$1 break;
}
client_header_timeout 60; client_header_timeout 60;
client_body_timeout 60; client_body_timeout 60;
keepalive_timeout 60; keepalive_timeout 60;

View File

@ -95,15 +95,37 @@ server {
} }
location / { location / {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_connect_timeout 300; proxy_connect_timeout 300;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Connection ""; proxy_set_header Connection "";
chunked_transfer_encoding off; chunked_transfer_encoding off;
proxy_pass http://127.0.0.1:9000;
proxy_pass http://127.0.0.1:9000;
}
location /files/signed/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# IMPORTANT: Signed urls will inspect the host header of the request.
# Normally a signed url will need to be generated with a specified client host in mind.
# To support dynamic hosts, e.g. some unknown self-hosted installation url,
# use a predefined host header. The host 'minio-service' is also used at the time of url signing.
proxy_set_header Host minio-service;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://127.0.0.1:9000;
rewrite ^/files/signed/(.*)$ /$1 break;
} }
client_header_timeout 60; client_header_timeout 60;

View File

@ -10,7 +10,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000 [[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production [[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU [[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS" [[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
[[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app [[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app
[[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379 [[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379
[[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1 [[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1

View File

@ -1,5 +1,5 @@
{ {
"version": "2.2.26", "version": "2.2.27-alpha.0",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -25,6 +25,7 @@
"bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh", "bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh",
"build": "lerna run build", "build": "lerna run build",
"build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput", "build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput",
"build:backend": "lerna run build --ignore @budibase/client --ignore @budibase/bbui --ignore @budibase/builder --ignore @budibase/cli",
"build:sdk": "lerna run build:sdk", "build:sdk": "lerna run build:sdk",
"deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular", "deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular",
"release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro", "release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro",
@ -44,16 +45,12 @@
"dev:server": "yarn run kill-server && lerna run --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server", "dev:server": "yarn run kill-server && lerna run --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server",
"test": "lerna run test && yarn test:pro", "test": "lerna run test && yarn test:pro",
"test:pro": "bash scripts/pro/test.sh", "test:pro": "bash scripts/pro/test.sh",
"lint:eslint": "eslint packages", "lint:eslint": "eslint packages && eslint qa-core",
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\"", "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",
"lint": "yarn run lint:eslint && yarn run lint:prettier", "lint": "yarn run lint:eslint && yarn run lint:prettier",
"lint:fix:eslint": "eslint --fix packages qa-core", "lint:fix:eslint": "eslint --fix packages qa-core",
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"", "lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint", "lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
"test:e2e": "lerna run cy:test --stream",
"test:e2e:ci": "lerna run cy:ci --stream",
"test:e2e:ci:record": "lerna run cy:ci:record --stream",
"test:e2e:ci:notify": "lerna run cy:ci:notify",
"build:specs": "lerna run specs", "build:specs": "lerna run specs",
"build:docker": "lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -", "build:docker": "lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
"build:docker:pre": "lerna run build && lerna run predocker", "build:docker:pre": "lerna run build && lerna run predocker",

View File

@ -3,7 +3,10 @@ const mockS3 = {
deleteObject: jest.fn().mockReturnThis(), deleteObject: jest.fn().mockReturnThis(),
deleteObjects: jest.fn().mockReturnThis(), deleteObjects: jest.fn().mockReturnThis(),
createBucket: jest.fn().mockReturnThis(), createBucket: jest.fn().mockReturnThis(),
listObjects: jest.fn().mockReturnThis(), listObject: jest.fn().mockReturnThis(),
getSignedUrl: jest.fn((operation: string, params: any) => {
return `http://s3.example.com/${params.Bucket}/${params.Key}`
}),
promise: jest.fn().mockReturnThis(), promise: jest.fn().mockReturnThis(),
catch: jest.fn(), catch: jest.fn(),
} }

View File

@ -6,6 +6,9 @@ const config: Config.InitialOptions = {
setupFiles: ["./tests/jestSetup.ts"], setupFiles: ["./tests/jestSetup.ts"],
collectCoverageFrom: ["src/**/*.{js,ts}"], collectCoverageFrom: ["src/**/*.{js,ts}"],
coverageReporters: ["lcov", "json", "clover"], coverageReporters: ["lcov", "json", "clover"],
transform: {
"^.+\\.ts?$": "@swc/jest",
},
} }
if (!process.env.CI) { if (!process.env.CI) {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "2.2.26", "version": "2.2.27-alpha.0",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -15,24 +15,28 @@
"prebuild": "rimraf dist/", "prebuild": "rimraf dist/",
"prepack": "cp package.json dist", "prepack": "cp package.json dist",
"build": "tsc -p tsconfig.build.json", "build": "tsc -p tsconfig.build.json",
"build:pro": "../../scripts/pro/build.sh",
"postbuild": "yarn run build:pro",
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput", "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
"test": "jest --coverage --maxWorkers=2", "test": "jest --coverage --maxWorkers=2",
"test:watch": "jest --watchAll" "test:watch": "jest --watchAll"
}, },
"dependencies": { "dependencies": {
"@budibase/nano": "10.1.1", "@budibase/nano": "10.1.1",
"@budibase/types": "^2.2.26", "@budibase/types": "2.2.27-alpha.0",
"@shopify/jest-koa-mocks": "5.0.1", "@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0",
"aws-sdk": "2.1030.0", "aws-sdk": "2.1030.0",
"bcrypt": "5.0.1", "bcrypt": "5.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bull": "4.10.1", "bull": "4.10.1",
"correlation-id": "4.0.0",
"dotenv": "16.0.1", "dotenv": "16.0.1",
"emitter-listener": "1.1.2", "emitter-listener": "1.1.2",
"ioredis": "4.28.0", "ioredis": "4.28.0",
"joi": "17.6.0", "joi": "17.6.0",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "9.0.0",
"koa-passport": "4.1.4", "koa-passport": "4.1.4",
"lodash": "4.17.21", "lodash": "4.17.21",
"lodash.isarguments": "3.1.0", "lodash.isarguments": "3.1.0",
@ -53,19 +57,23 @@
"zlib": "1.0.5" "zlib": "1.0.5"
}, },
"devDependencies": { "devDependencies": {
"@swc/core": "^1.3.25",
"@swc/jest": "^0.2.24",
"@types/chance": "1.1.3", "@types/chance": "1.1.3",
"@types/ioredis": "4.28.0", "@types/ioredis": "4.28.0",
"@types/jest": "27.5.1", "@types/jest": "27.5.1",
"@types/koa": "2.13.4", "@types/koa": "2.13.4",
"@types/koa-pino-logger": "3.0.0",
"@types/lodash": "4.14.180", "@types/lodash": "4.14.180",
"@types/node": "14.18.20", "@types/node": "14.18.20",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/pino-http": "5.8.1",
"@types/pouchdb": "6.4.0", "@types/pouchdb": "6.4.0",
"@types/redlock": "4.0.3", "@types/redlock": "4.0.3",
"@types/semver": "7.3.7", "@types/semver": "7.3.7",
"@types/tar-fs": "2.0.1", "@types/tar-fs": "2.0.1",
"@types/uuid": "8.3.4", "@types/uuid": "8.3.4",
"chance": "1.1.3", "chance": "1.1.8",
"ioredis-mock": "5.8.0", "ioredis-mock": "5.8.0",
"jest": "28.1.1", "jest": "28.1.1",
"koa": "2.13.4", "koa": "2.13.4",

View File

@ -7,7 +7,7 @@ function generateTenantKey(key: string) {
return `${key}:${tenantId}` return `${key}:${tenantId}`
} }
export = class BaseCache { export default class BaseCache {
client: Client | undefined client: Client | undefined
constructor(client: Client | undefined = undefined) { constructor(client: Client | undefined = undefined) {

View File

@ -1,6 +1,6 @@
const BaseCache = require("./base") const BaseCache = require("./base")
const GENERIC = new BaseCache() const GENERIC = new BaseCache.default()
export enum CacheKey { export enum CacheKey {
CHECKLIST = "checklist", CHECKLIST = "checklist",

View File

@ -1,6 +1,7 @@
import fetch from "node-fetch" import fetch from "node-fetch"
import * as logging from "../logging"
export = class API { export default class API {
host: string host: string
constructor(host: string) { constructor(host: string) {
@ -22,6 +23,9 @@ export = class API {
let json = options.headers["Content-Type"] === "application/json" let json = options.headers["Content-Type"] === "application/json"
// add x-budibase-correlation-id header
logging.correlation.setHeader(options.headers)
const requestOptions = { const requestOptions = {
method: method, method: method,
body: json ? JSON.stringify(options.body) : options.body, body: json ? JSON.stringify(options.body) : options.body,

View File

@ -77,6 +77,7 @@ export const StaticDatabases = {
apiKeys: "apikeys", apiKeys: "apikeys",
usageQuota: "usage_quota", usageQuota: "usage_quota",
licenseInfo: "license_info", licenseInfo: "license_info",
environmentVariables: "environmentvariables",
}, },
}, },
// contains information about tenancy and so on // contains information about tenancy and so on

View File

@ -22,6 +22,7 @@ export enum Header {
TENANT_ID = "x-budibase-tenant-id", TENANT_ID = "x-budibase-tenant-id",
TOKEN = "x-budibase-token", TOKEN = "x-budibase-token",
CSRF_TOKEN = "x-csrf-token", CSRF_TOKEN = "x-csrf-token",
CORRELATION_ID = "x-budibase-correlation-id",
} }
export enum GlobalRole { export enum GlobalRole {

View File

@ -1,17 +1,14 @@
import { AsyncLocalStorage } from "async_hooks" import { AsyncLocalStorage } from "async_hooks"
import { ContextMap } from "./mainContext"
export default class Context { export default class Context {
static storage = new AsyncLocalStorage<Record<string, any>>() static storage = new AsyncLocalStorage<ContextMap>()
static run(context: Record<string, any>, func: any) { static run(context: ContextMap, func: any) {
return Context.storage.run(context, () => func()) return Context.storage.run(context, () => func())
} }
static get(): Record<string, any> { static get(): ContextMap {
return Context.storage.getStore() as Record<string, any> return Context.storage.getStore() as ContextMap
}
static set(context: Record<string, any>) {
Context.storage.enterWith(context)
} }
} }

View File

@ -2,7 +2,7 @@
// store an app ID to pretend there is a context // store an app ID to pretend there is a context
import env from "../environment" import env from "../environment"
import Context from "./Context" import Context from "./Context"
import { getDevelopmentAppID, getProdAppID } from "../db/conversions" import * as conversions from "../db/conversions"
import { getDB } from "../db/db" import { getDB } from "../db/db"
import { import {
DocumentType, DocumentType,
@ -16,6 +16,7 @@ export type ContextMap = {
tenantId?: string tenantId?: string
appId?: string appId?: string
identity?: IdentityContext identity?: IdentityContext
environmentVariables?: Record<string, string>
} }
let TEST_APP_ID: string | null = null let TEST_APP_ID: string | null = null
@ -75,7 +76,7 @@ export function getTenantIDFromAppID(appId: string) {
} }
} }
function updateContext(updates: ContextMap) { function updateContext(updates: ContextMap): ContextMap {
let context: ContextMap let context: ContextMap
try { try {
context = Context.get() context = Context.get()
@ -120,15 +121,23 @@ export async function doInTenant(
return newContext(updates, task) return newContext(updates, task)
} }
export async function doInAppContext(appId: string, task: any): Promise<any> { export async function doInAppContext(
if (!appId) { appId: string | null,
task: any
): Promise<any> {
if (!appId && !env.isTest()) {
throw new Error("appId is required") throw new Error("appId is required")
} }
const tenantId = getTenantIDFromAppID(appId) let updates: ContextMap
const updates: ContextMap = { appId } if (!appId) {
if (tenantId) { updates = { appId: "" }
updates.tenantId = tenantId } else {
const tenantId = getTenantIDFromAppID(appId)
updates = { appId }
if (tenantId) {
updates.tenantId = tenantId
}
} }
return newContext(updates, task) return newContext(updates, task)
} }
@ -181,25 +190,33 @@ export function getAppId(): string | undefined {
} }
} }
export function updateTenantId(tenantId?: string) { export const getProdAppId = () => {
let context: ContextMap = updateContext({ const appId = getAppId()
tenantId, if (!appId) {
}) throw new Error("Could not get appId")
Context.set(context) }
return conversions.getProdAppID(appId)
} }
export function updateAppId(appId: string) { export function doInEnvironmentContext(
let context: ContextMap = updateContext({ values: Record<string, string>,
appId, task: any
}) ) {
try { if (!values) {
Context.set(context) throw new Error("Must supply environment variables.")
} catch (err) { }
if (env.isTest()) { const updates = {
TEST_APP_ID = appId environmentVariables: values,
} else { }
throw err return newContext(updates, task)
} }
export function getEnvironmentVariables() {
const context = Context.get()
if (!context.environmentVariables) {
return null
} else {
return context.environmentVariables
} }
} }
@ -229,7 +246,7 @@ export function getProdAppDB(opts?: any): Database {
if (!appId) { if (!appId) {
throw new Error("Unable to retrieve prod DB - no app ID.") throw new Error("Unable to retrieve prod DB - no app ID.")
} }
return getDB(getProdAppID(appId), opts) return getDB(conversions.getProdAppID(appId), opts)
} }
/** /**
@ -241,5 +258,5 @@ export function getDevAppDB(opts?: any): Database {
if (!appId) { if (!appId) {
throw new Error("Unable to retrieve dev DB - no app ID.") throw new Error("Unable to retrieve dev DB - no app ID.")
} }
return getDB(getDevelopmentAppID(appId), opts) return getDB(conversions.getDevelopmentAppID(appId), opts)
} }

View File

@ -1,7 +1,7 @@
require("../../../tests") require("../../../tests")
const context = require("../") const context = require("../")
const { DEFAULT_TENANT_ID } = require("../../constants") const { DEFAULT_TENANT_ID } = require("../../constants")
const env = require("../../environment") import env from "../../environment"
describe("context", () => { describe("context", () => {
describe("doInTenant", () => { describe("doInTenant", () => {
@ -26,7 +26,7 @@ describe("context", () => {
it("fails when no tenant id is set", () => { it("fails when no tenant id is set", () => {
const test = () => { const test = () => {
let error let error: any
try { try {
context.getTenantId() context.getTenantId()
} catch (e) { } catch (e) {
@ -45,7 +45,7 @@ describe("context", () => {
it("fails when no tenant db is set", () => { it("fails when no tenant db is set", () => {
const test = () => { const test = () => {
let error let error: any
try { try {
context.getGlobalDB() context.getGlobalDB()
} catch (e) { } catch (e) {

View File

@ -15,18 +15,47 @@ import { getCouchInfo } from "./connections"
import { directCouchCall } from "./utils" import { directCouchCall } from "./utils"
import { getPouchDB } from "./pouchDB" import { getPouchDB } from "./pouchDB"
import { WriteStream, ReadStream } from "fs" import { WriteStream, ReadStream } from "fs"
import { newid } from "../../newid"
function buildNano(couchInfo: { url: string; cookie: string }) {
return Nano({
url: couchInfo.url,
requestDefaults: {
headers: {
Authorization: couchInfo.cookie,
},
},
parseUrl: false,
})
}
export function DatabaseWithConnection(
dbName: string,
connection: string,
opts?: DatabaseOpts
) {
if (!connection) {
throw new Error("Must provide connection details")
}
return new DatabaseImpl(dbName, opts, connection)
}
export class DatabaseImpl implements Database { export class DatabaseImpl implements Database {
public readonly name: string public readonly name: string
private static nano: Nano.ServerScope private static nano: Nano.ServerScope
private readonly instanceNano?: Nano.ServerScope
private readonly pouchOpts: DatabaseOpts private readonly pouchOpts: DatabaseOpts
constructor(dbName?: string, opts?: DatabaseOpts) { constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
if (dbName == null) { if (dbName == null) {
throw new Error("Database name cannot be undefined.") throw new Error("Database name cannot be undefined.")
} }
this.name = dbName this.name = dbName
this.pouchOpts = opts || {} this.pouchOpts = opts || {}
if (connection) {
const couchInfo = getCouchInfo(connection)
this.instanceNano = buildNano(couchInfo)
}
if (!DatabaseImpl.nano) { if (!DatabaseImpl.nano) {
DatabaseImpl.init() DatabaseImpl.init()
} }
@ -34,15 +63,7 @@ export class DatabaseImpl implements Database {
static init() { static init() {
const couchInfo = getCouchInfo() const couchInfo = getCouchInfo()
DatabaseImpl.nano = Nano({ DatabaseImpl.nano = buildNano(couchInfo)
url: couchInfo.url,
requestDefaults: {
headers: {
Authorization: couchInfo.cookie,
},
},
parseUrl: false,
})
} }
async exists() { async exists() {
@ -50,6 +71,10 @@ export class DatabaseImpl implements Database {
return response.status === 200 return response.status === 200
} }
private nano() {
return this.instanceNano || DatabaseImpl.nano
}
async checkSetup() { async checkSetup() {
let shouldCreate = !this.pouchOpts?.skip_setup let shouldCreate = !this.pouchOpts?.skip_setup
// check exists in a lightweight fashion // check exists in a lightweight fashion
@ -58,9 +83,9 @@ export class DatabaseImpl implements Database {
throw new Error("DB does not exist") throw new Error("DB does not exist")
} }
if (!exists) { if (!exists) {
await DatabaseImpl.nano.db.create(this.name) await this.nano().db.create(this.name)
} }
return DatabaseImpl.nano.db.use(this.name) return this.nano().db.use(this.name)
} }
private async updateOutput(fnc: any) { private async updateOutput(fnc: any) {
@ -101,6 +126,13 @@ export class DatabaseImpl implements Database {
return this.updateOutput(() => db.destroy(_id, _rev)) return this.updateOutput(() => db.destroy(_id, _rev))
} }
async post(document: AnyDocument, opts?: DatabasePutOpts) {
if (!document._id) {
document._id = newid()
}
return this.put(document, opts)
}
async put(document: AnyDocument, opts?: DatabasePutOpts) { async put(document: AnyDocument, opts?: DatabasePutOpts) {
if (!document._id) { if (!document._id) {
throw new Error("Cannot store document without _id field.") throw new Error("Cannot store document without _id field.")
@ -146,7 +178,7 @@ export class DatabaseImpl implements Database {
async destroy() { async destroy() {
try { try {
await DatabaseImpl.nano.db.destroy(this.name) await this.nano().db.destroy(this.name)
} catch (err: any) { } catch (err: any) {
// didn't exist, don't worry // didn't exist, don't worry
if (err.statusCode === 404) { if (err.statusCode === 404) {

View File

@ -1,7 +1,7 @@
import env from "../../environment" import env from "../../environment"
export const getCouchInfo = () => { export const getCouchInfo = (connection?: string) => {
const urlInfo = getUrlInfo() const urlInfo = getUrlInfo(connection)
let username let username
let password let password
if (env.COUCH_DB_USERNAME) { if (env.COUCH_DB_USERNAME) {

View File

@ -5,18 +5,13 @@ const {
isDevAppID, isDevAppID,
isProdAppID, isProdAppID,
} = require("../conversions") } = require("../conversions")
const { const { generateAppID, getPlatformUrl, getScopedConfig } = require("../utils")
generateAppID,
getPlatformUrl,
getScopedConfig
} = require("../utils")
const tenancy = require("../../tenancy") const tenancy = require("../../tenancy")
const { Config, DEFAULT_TENANT_ID } = require("../../constants") const { Config, DEFAULT_TENANT_ID } = require("../../constants")
const env = require("../../environment") import env from "../../environment"
describe("utils", () => { describe("utils", () => {
describe("app ID manipulation", () => { describe("app ID manipulation", () => {
function getID() { function getID() {
const appId = generateAppID() const appId = generateAppID()
const split = appId.split("_") const split = appId.split("_")
@ -81,8 +76,8 @@ const setDbPlatformUrl = async () => {
_id: "config_settings", _id: "config_settings",
type: Config.SETTINGS, type: Config.SETTINGS,
config: { config: {
platformUrl: DB_URL platformUrl: DB_URL,
} },
}) })
} }
@ -92,7 +87,7 @@ const clearSettingsConfig = async () => {
try { try {
const config = await db.get("config_settings") const config = await db.get("config_settings")
await db.remove("config_settings", config._rev) await db.remove("config_settings", config._rev)
} catch (e) { } catch (e: any) {
if (e.status !== 404) { if (e.status !== 404) {
throw e throw e
} }
@ -102,7 +97,6 @@ const clearSettingsConfig = async () => {
describe("getPlatformUrl", () => { describe("getPlatformUrl", () => {
describe("self host", () => { describe("self host", () => {
beforeEach(async () => { beforeEach(async () => {
env._set("SELF_HOST", 1) env._set("SELF_HOST", 1)
await clearSettingsConfig() await clearSettingsConfig()
@ -132,7 +126,6 @@ describe("getPlatformUrl", () => {
}) })
}) })
describe("cloud", () => { describe("cloud", () => {
const TENANT_AWARE_URL = "http://default.env.com" const TENANT_AWARE_URL = "http://default.env.com"
@ -169,7 +162,6 @@ describe("getPlatformUrl", () => {
describe("getScopedConfig", () => { describe("getScopedConfig", () => {
describe("settings config", () => { describe("settings config", () => {
beforeEach(async () => { beforeEach(async () => {
env._set("SELF_HOSTED", 1) env._set("SELF_HOSTED", 1)
env._set("PLATFORM_URL", "") env._set("PLATFORM_URL", "")

View File

@ -14,7 +14,7 @@ import { doWithDB, allDbs, directCouchAllDbs } from "./db"
import { getAppMetadata } from "../cache/appMetadata" import { getAppMetadata } from "../cache/appMetadata"
import { isDevApp, isDevAppID, getProdAppID } from "./conversions" import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
import * as events from "../events" import * as events from "../events"
import { App, Database, ConfigType } from "@budibase/types" import { App, Database, ConfigType, isSettingsConfig } from "@budibase/types"
/** /**
* Generates a new app ID. * Generates a new app ID.
@ -489,18 +489,12 @@ export const getScopedFullConfig = async function (
// custom logic for settings doc // custom logic for settings doc
if (type === ConfigType.SETTINGS) { if (type === ConfigType.SETTINGS) {
if (scopedConfig && scopedConfig.doc) { if (!scopedConfig || !scopedConfig.doc) {
// overrides affected by environment variables
scopedConfig.doc.config.platformUrl = await getPlatformUrl({
tenantAware: true,
})
scopedConfig.doc.config.analyticsEnabled =
await events.analytics.enabled()
} else {
// defaults // defaults
scopedConfig = { scopedConfig = {
doc: { doc: {
_id: generateConfigID({ type, user, workspace }), _id: generateConfigID({ type, user, workspace }),
type: ConfigType.SETTINGS,
config: { config: {
platformUrl: await getPlatformUrl({ tenantAware: true }), platformUrl: await getPlatformUrl({ tenantAware: true }),
analyticsEnabled: await events.analytics.enabled(), analyticsEnabled: await events.analytics.enabled(),
@ -508,6 +502,16 @@ export const getScopedFullConfig = async function (
}, },
} }
} }
// will always be true - use assertion function to get type access
if (isSettingsConfig(scopedConfig.doc)) {
// overrides affected by environment
scopedConfig.doc.config.platformUrl = await getPlatformUrl({
tenantAware: true,
})
scopedConfig.doc.config.analyticsEnabled =
await events.analytics.enabled()
}
} }
return scopedConfig && scopedConfig.doc return scopedConfig && scopedConfig.doc

View File

@ -1,9 +1,13 @@
function isTest() { function isTest() {
return ( return isCypress() || isJest()
process.env.NODE_ENV === "jest" || }
process.env.NODE_ENV === "cypress" ||
process.env.JEST_WORKER_ID != null function isJest() {
) return !!(process.env.NODE_ENV === "jest" || process.env.JEST_WORKER_ID)
}
function isCypress() {
return process.env.NODE_ENV === "cypress"
} }
function isDev() { function isDev() {
@ -21,15 +25,19 @@ const DefaultBucketName = {
APPS: "prod-budi-app-assets", APPS: "prod-budi-app-assets",
TEMPLATES: "templates", TEMPLATES: "templates",
GLOBAL: "global", GLOBAL: "global",
CLOUD: "prod-budi-tenant-uploads",
PLUGINS: "plugins", PLUGINS: "plugins",
} }
const environment = { const environment = {
isTest, isTest,
isJest,
isDev, isDev,
isProd: () => {
return !isDev()
},
JS_BCRYPT: process.env.JS_BCRYPT, JS_BCRYPT: process.env.JS_BCRYPT,
JWT_SECRET: process.env.JWT_SECRET, JWT_SECRET: process.env.JWT_SECRET,
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005", COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005",
COUCH_DB_USERNAME: process.env.COUCH_DB_USER, COUCH_DB_USERNAME: process.env.COUCH_DB_USER,
COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD,
@ -42,6 +50,7 @@ const environment = {
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
AWS_REGION: process.env.AWS_REGION, AWS_REGION: process.env.AWS_REGION,
MINIO_URL: process.env.MINIO_URL, MINIO_URL: process.env.MINIO_URL,
MINIO_ENABLED: process.env.MINIO_ENABLED || 1,
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
MULTI_TENANCY: process.env.MULTI_TENANCY, MULTI_TENANCY: process.env.MULTI_TENANCY,
ACCOUNT_PORTAL_URL: ACCOUNT_PORTAL_URL:
@ -54,6 +63,9 @@ const environment = {
POSTHOG_TOKEN: process.env.POSTHOG_TOKEN, POSTHOG_TOKEN: process.env.POSTHOG_TOKEN,
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS, TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS,
CLOUDFRONT_CDN: process.env.CLOUDFRONT_CDN,
CLOUDFRONT_PRIVATE_KEY_64: process.env.CLOUDFRONT_PRIVATE_KEY_64,
CLOUDFRONT_PUBLIC_KEY_ID: process.env.CLOUDFRONT_PUBLIC_KEY_ID,
BACKUPS_BUCKET_NAME: BACKUPS_BUCKET_NAME:
process.env.BACKUPS_BUCKET_NAME || DefaultBucketName.BACKUPS, process.env.BACKUPS_BUCKET_NAME || DefaultBucketName.BACKUPS,
APPS_BUCKET_NAME: process.env.APPS_BUCKET_NAME || DefaultBucketName.APPS, APPS_BUCKET_NAME: process.env.APPS_BUCKET_NAME || DefaultBucketName.APPS,
@ -61,12 +73,9 @@ const environment = {
process.env.TEMPLATES_BUCKET_NAME || DefaultBucketName.TEMPLATES, process.env.TEMPLATES_BUCKET_NAME || DefaultBucketName.TEMPLATES,
GLOBAL_BUCKET_NAME: GLOBAL_BUCKET_NAME:
process.env.GLOBAL_BUCKET_NAME || DefaultBucketName.GLOBAL, process.env.GLOBAL_BUCKET_NAME || DefaultBucketName.GLOBAL,
GLOBAL_CLOUD_BUCKET_NAME:
process.env.GLOBAL_CLOUD_BUCKET_NAME || DefaultBucketName.CLOUD,
PLUGIN_BUCKET_NAME: PLUGIN_BUCKET_NAME:
process.env.PLUGIN_BUCKET_NAME || DefaultBucketName.PLUGINS, process.env.PLUGIN_BUCKET_NAME || DefaultBucketName.PLUGINS,
USE_COUCH: process.env.USE_COUCH || true, USE_COUCH: process.env.USE_COUCH || true,
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
DEFAULT_LICENSE: process.env.DEFAULT_LICENSE, DEFAULT_LICENSE: process.env.DEFAULT_LICENSE,
SERVICE: process.env.SERVICE || "budibase", SERVICE: process.env.SERVICE || "budibase",
LOG_LEVEL: process.env.LOG_LEVEL, LOG_LEVEL: process.env.LOG_LEVEL,
@ -87,6 +96,11 @@ for (let [key, value] of Object.entries(environment)) {
// @ts-ignore // @ts-ignore
environment[key] = 0 environment[key] = 0
} }
// handle the edge case of "false" to disable an environment variable
if (value === "false") {
// @ts-ignore
environment[key] = 0
}
} }
export = environment export default environment

View File

@ -0,0 +1,37 @@
import * as licensing from "./licensing"
// combine all error codes into single object
export const codes = {
...licensing.codes,
}
// combine all error types
export const types = [licensing.type]
// combine all error contexts
const context = {
...licensing.context,
}
// derive a public error message using codes, types and any custom contexts
export const getPublicError = (err: any) => {
let error
if (err.code || err.type) {
// add generic error information
error = {
code: err.code,
type: err.type,
}
if (err.code && context[err.code]) {
error = {
...error,
// get any additional context from this error
...context[err.code](err),
}
}
}
return error
}

View File

@ -1,47 +1,3 @@
import { HTTPError } from "./http" export * from "./errors"
import { UsageLimitError, FeatureDisabledError } from "./licensing" export { UsageLimitError, FeatureDisabledError } from "./licensing"
import * as licensing from "./licensing" export { HTTPError } from "./http"
const codes = {
...licensing.codes,
}
const types = [licensing.type]
const context = {
...licensing.context,
}
const getPublicError = (err: any) => {
let error
if (err.code || err.type) {
// add generic error information
error = {
code: err.code,
type: err.type,
}
if (err.code && context[err.code]) {
error = {
...error,
// get any additional context from this error
...context[err.code](err),
}
}
}
return error
}
const pkg = {
codes,
types,
errors: {
UsageLimitError,
FeatureDisabledError,
HTTPError,
},
getPublicError,
}
export = pkg

View File

@ -1,6 +1,6 @@
import { Event } from "@budibase/types" import { Event } from "@budibase/types"
import { processors } from "./processors" import { processors } from "./processors"
import * as identification from "./identification" import identification from "./identification"
import * as backfill from "./backfill" import * as backfill from "./backfill"
export const publishEvent = async ( export const publishEvent = async (

View File

@ -33,7 +33,7 @@ const pkg = require("../../package.json")
* - tenant * - tenant
* - installation * - installation
*/ */
export const getCurrentIdentity = async (): Promise<Identity> => { const getCurrentIdentity = async (): Promise<Identity> => {
let identityContext = identityCtx.getIdentity() let identityContext = identityCtx.getIdentity()
const environment = getDeploymentEnvironment() const environment = getDeploymentEnvironment()
@ -94,7 +94,7 @@ export const getCurrentIdentity = async (): Promise<Identity> => {
} }
} }
export const identifyInstallationGroup = async ( const identifyInstallationGroup = async (
installId: string, installId: string,
timestamp?: string | number timestamp?: string | number
): Promise<void> => { ): Promise<void> => {
@ -118,7 +118,7 @@ export const identifyInstallationGroup = async (
await identify({ ...group, id: `$${type}_${id}` }, timestamp) await identify({ ...group, id: `$${type}_${id}` }, timestamp)
} }
export const identifyTenantGroup = async ( const identifyTenantGroup = async (
tenantId: string, tenantId: string,
account: Account | undefined, account: Account | undefined,
timestamp?: string | number timestamp?: string | number
@ -156,7 +156,7 @@ export const identifyTenantGroup = async (
await identify({ ...group, id: `$${type}_${id}` }, timestamp) await identify({ ...group, id: `$${type}_${id}` }, timestamp)
} }
export const identifyUser = async ( const identifyUser = async (
user: User, user: User,
account: CloudAccount | undefined, account: CloudAccount | undefined,
timestamp?: string | number timestamp?: string | number
@ -191,7 +191,7 @@ export const identifyUser = async (
await identify(identity, timestamp) await identify(identity, timestamp)
} }
export const identifyAccount = async (account: Account) => { const identifyAccount = async (account: Account) => {
let id = account.accountId let id = account.accountId
const tenantId = account.tenantId const tenantId = account.tenantId
let type = IdentityType.USER let type = IdentityType.USER
@ -224,17 +224,11 @@ export const identifyAccount = async (account: Account) => {
await identify(identity) await identify(identity)
} }
export const identify = async ( const identify = async (identity: Identity, timestamp?: string | number) => {
identity: Identity,
timestamp?: string | number
) => {
await processors.identify(identity, timestamp) await processors.identify(identity, timestamp)
} }
export const identifyGroup = async ( const identifyGroup = async (group: Group, timestamp?: string | number) => {
group: Group,
timestamp?: string | number
) => {
await processors.identifyGroup(group, timestamp) await processors.identifyGroup(group, timestamp)
} }
@ -250,7 +244,7 @@ const getHostingFromEnv = () => {
return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD
} }
export const getInstallationId = async () => { const getInstallationId = async () => {
if (isAccountPortal()) { if (isAccountPortal()) {
return "account-portal" return "account-portal"
} }
@ -300,3 +294,14 @@ const formatDistinctId = (id: string, type: IdentityType) => {
return id return id
} }
} }
export default {
getCurrentIdentity,
identifyInstallationGroup,
identifyTenantGroup,
identifyUser,
identifyAccount,
identify,
identifyGroup,
getInstallationId,
}

View File

@ -1,7 +1,7 @@
export * from "./publishers" export * from "./publishers"
export * as processors from "./processors" export * as processors from "./processors"
export * as analytics from "./analytics" export * as analytics from "./analytics"
export * as identification from "./identification" export { default as identification } from "./identification"
export * as backfillCache from "./backfill" export * as backfillCache from "./backfill"
import { processors } from "./processors" import { processors } from "./processors"

View File

@ -23,7 +23,7 @@ export default class LoggingProcessor implements EventProcessor {
return return
} }
let timestampString = getTimestampString(timestamp) let timestampString = getTimestampString(timestamp)
let message = `[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} ` let message = `[audit] [identityType=${identity.type}] ${timestampString} ${event} `
if (env.isDev()) { if (env.isDev()) {
message = message + `[debug: [properties=${JSON.stringify(properties)}] ]` message = message + `[debug: [properties=${JSON.stringify(properties)}] ]`
} }

View File

@ -7,23 +7,29 @@ import {
AccountVerifiedEvent, AccountVerifiedEvent,
} from "@budibase/types" } from "@budibase/types"
export async function created(account: Account) { async function created(account: Account) {
const properties: AccountCreatedEvent = { const properties: AccountCreatedEvent = {
tenantId: account.tenantId, tenantId: account.tenantId,
} }
await publishEvent(Event.ACCOUNT_CREATED, properties) await publishEvent(Event.ACCOUNT_CREATED, properties)
} }
export async function deleted(account: Account) { async function deleted(account: Account) {
const properties: AccountDeletedEvent = { const properties: AccountDeletedEvent = {
tenantId: account.tenantId, tenantId: account.tenantId,
} }
await publishEvent(Event.ACCOUNT_DELETED, properties) await publishEvent(Event.ACCOUNT_DELETED, properties)
} }
export async function verified(account: Account) { async function verified(account: Account) {
const properties: AccountVerifiedEvent = { const properties: AccountVerifiedEvent = {
tenantId: account.tenantId, tenantId: account.tenantId,
} }
await publishEvent(Event.ACCOUNT_VERIFIED, properties) await publishEvent(Event.ACCOUNT_VERIFIED, properties)
} }
export default {
created,
deleted,
verified,
}

View File

@ -15,7 +15,7 @@ import {
AppExportedEvent, AppExportedEvent,
} from "@budibase/types" } from "@budibase/types"
export const created = async (app: App, timestamp?: string | number) => { const created = async (app: App, timestamp?: string | number) => {
const properties: AppCreatedEvent = { const properties: AppCreatedEvent = {
appId: app.appId, appId: app.appId,
version: app.version, version: app.version,
@ -23,7 +23,7 @@ export const created = async (app: App, timestamp?: string | number) => {
await publishEvent(Event.APP_CREATED, properties, timestamp) await publishEvent(Event.APP_CREATED, properties, timestamp)
} }
export async function updated(app: App) { async function updated(app: App) {
const properties: AppUpdatedEvent = { const properties: AppUpdatedEvent = {
appId: app.appId, appId: app.appId,
version: app.version, version: app.version,
@ -31,35 +31,35 @@ export async function updated(app: App) {
await publishEvent(Event.APP_UPDATED, properties) await publishEvent(Event.APP_UPDATED, properties)
} }
export async function deleted(app: App) { async function deleted(app: App) {
const properties: AppDeletedEvent = { const properties: AppDeletedEvent = {
appId: app.appId, appId: app.appId,
} }
await publishEvent(Event.APP_DELETED, properties) await publishEvent(Event.APP_DELETED, properties)
} }
export async function published(app: App, timestamp?: string | number) { async function published(app: App, timestamp?: string | number) {
const properties: AppPublishedEvent = { const properties: AppPublishedEvent = {
appId: app.appId, appId: app.appId,
} }
await publishEvent(Event.APP_PUBLISHED, properties, timestamp) await publishEvent(Event.APP_PUBLISHED, properties, timestamp)
} }
export async function unpublished(app: App) { async function unpublished(app: App) {
const properties: AppUnpublishedEvent = { const properties: AppUnpublishedEvent = {
appId: app.appId, appId: app.appId,
} }
await publishEvent(Event.APP_UNPUBLISHED, properties) await publishEvent(Event.APP_UNPUBLISHED, properties)
} }
export async function fileImported(app: App) { async function fileImported(app: App) {
const properties: AppFileImportedEvent = { const properties: AppFileImportedEvent = {
appId: app.appId, appId: app.appId,
} }
await publishEvent(Event.APP_FILE_IMPORTED, properties) await publishEvent(Event.APP_FILE_IMPORTED, properties)
} }
export async function templateImported(app: App, templateKey: string) { async function templateImported(app: App, templateKey: string) {
const properties: AppTemplateImportedEvent = { const properties: AppTemplateImportedEvent = {
appId: app.appId, appId: app.appId,
templateKey, templateKey,
@ -67,7 +67,7 @@ export async function templateImported(app: App, templateKey: string) {
await publishEvent(Event.APP_TEMPLATE_IMPORTED, properties) await publishEvent(Event.APP_TEMPLATE_IMPORTED, properties)
} }
export async function versionUpdated( async function versionUpdated(
app: App, app: App,
currentVersion: string, currentVersion: string,
updatedToVersion: string updatedToVersion: string
@ -80,7 +80,7 @@ export async function versionUpdated(
await publishEvent(Event.APP_VERSION_UPDATED, properties) await publishEvent(Event.APP_VERSION_UPDATED, properties)
} }
export async function versionReverted( async function versionReverted(
app: App, app: App,
currentVersion: string, currentVersion: string,
revertedToVersion: string revertedToVersion: string
@ -93,16 +93,30 @@ export async function versionReverted(
await publishEvent(Event.APP_VERSION_REVERTED, properties) await publishEvent(Event.APP_VERSION_REVERTED, properties)
} }
export async function reverted(app: App) { async function reverted(app: App) {
const properties: AppRevertedEvent = { const properties: AppRevertedEvent = {
appId: app.appId, appId: app.appId,
} }
await publishEvent(Event.APP_REVERTED, properties) await publishEvent(Event.APP_REVERTED, properties)
} }
export async function exported(app: App) { async function exported(app: App) {
const properties: AppExportedEvent = { const properties: AppExportedEvent = {
appId: app.appId, appId: app.appId,
} }
await publishEvent(Event.APP_EXPORTED, properties) await publishEvent(Event.APP_EXPORTED, properties)
} }
export default {
created,
updated,
deleted,
published,
unpublished,
fileImported,
templateImported,
versionUpdated,
versionReverted,
reverted,
exported,
}

View File

@ -12,7 +12,7 @@ import {
} from "@budibase/types" } from "@budibase/types"
import { identification } from ".." import { identification } from ".."
export async function login(source: LoginSource) { async function login(source: LoginSource) {
const identity = await identification.getCurrentIdentity() const identity = await identification.getCurrentIdentity()
const properties: LoginEvent = { const properties: LoginEvent = {
userId: identity.id, userId: identity.id,
@ -21,7 +21,7 @@ export async function login(source: LoginSource) {
await publishEvent(Event.AUTH_LOGIN, properties) await publishEvent(Event.AUTH_LOGIN, properties)
} }
export async function logout() { async function logout() {
const identity = await identification.getCurrentIdentity() const identity = await identification.getCurrentIdentity()
const properties: LogoutEvent = { const properties: LogoutEvent = {
userId: identity.id, userId: identity.id,
@ -29,30 +29,39 @@ export async function logout() {
await publishEvent(Event.AUTH_LOGOUT, properties) await publishEvent(Event.AUTH_LOGOUT, properties)
} }
export async function SSOCreated(type: SSOType, timestamp?: string | number) { async function SSOCreated(type: SSOType, timestamp?: string | number) {
const properties: SSOCreatedEvent = { const properties: SSOCreatedEvent = {
type, type,
} }
await publishEvent(Event.AUTH_SSO_CREATED, properties, timestamp) await publishEvent(Event.AUTH_SSO_CREATED, properties, timestamp)
} }
export async function SSOUpdated(type: SSOType) { async function SSOUpdated(type: SSOType) {
const properties: SSOUpdatedEvent = { const properties: SSOUpdatedEvent = {
type, type,
} }
await publishEvent(Event.AUTH_SSO_UPDATED, properties) await publishEvent(Event.AUTH_SSO_UPDATED, properties)
} }
export async function SSOActivated(type: SSOType, timestamp?: string | number) { async function SSOActivated(type: SSOType, timestamp?: string | number) {
const properties: SSOActivatedEvent = { const properties: SSOActivatedEvent = {
type, type,
} }
await publishEvent(Event.AUTH_SSO_ACTIVATED, properties, timestamp) await publishEvent(Event.AUTH_SSO_ACTIVATED, properties, timestamp)
} }
export async function SSODeactivated(type: SSOType) { async function SSODeactivated(type: SSOType) {
const properties: SSODeactivatedEvent = { const properties: SSODeactivatedEvent = {
type, type,
} }
await publishEvent(Event.AUTH_SSO_DEACTIVATED, properties) await publishEvent(Event.AUTH_SSO_DEACTIVATED, properties)
} }
export default {
login,
logout,
SSOCreated,
SSOUpdated,
SSOActivated,
SSODeactivated,
}

View File

@ -12,10 +12,7 @@ import {
AutomationsRunEvent, AutomationsRunEvent,
} from "@budibase/types" } from "@budibase/types"
export async function created( async function created(automation: Automation, timestamp?: string | number) {
automation: Automation,
timestamp?: string | number
) {
const properties: AutomationCreatedEvent = { const properties: AutomationCreatedEvent = {
appId: automation.appId, appId: automation.appId,
automationId: automation._id as string, automationId: automation._id as string,
@ -25,7 +22,7 @@ export async function created(
await publishEvent(Event.AUTOMATION_CREATED, properties, timestamp) await publishEvent(Event.AUTOMATION_CREATED, properties, timestamp)
} }
export async function triggerUpdated(automation: Automation) { async function triggerUpdated(automation: Automation) {
const properties: AutomationTriggerUpdatedEvent = { const properties: AutomationTriggerUpdatedEvent = {
appId: automation.appId, appId: automation.appId,
automationId: automation._id as string, automationId: automation._id as string,
@ -35,7 +32,7 @@ export async function triggerUpdated(automation: Automation) {
await publishEvent(Event.AUTOMATION_TRIGGER_UPDATED, properties) await publishEvent(Event.AUTOMATION_TRIGGER_UPDATED, properties)
} }
export async function deleted(automation: Automation) { async function deleted(automation: Automation) {
const properties: AutomationDeletedEvent = { const properties: AutomationDeletedEvent = {
appId: automation.appId, appId: automation.appId,
automationId: automation._id as string, automationId: automation._id as string,
@ -45,7 +42,7 @@ export async function deleted(automation: Automation) {
await publishEvent(Event.AUTOMATION_DELETED, properties) await publishEvent(Event.AUTOMATION_DELETED, properties)
} }
export async function tested(automation: Automation) { async function tested(automation: Automation) {
const properties: AutomationTestedEvent = { const properties: AutomationTestedEvent = {
appId: automation.appId, appId: automation.appId,
automationId: automation._id as string, automationId: automation._id as string,
@ -55,14 +52,14 @@ export async function tested(automation: Automation) {
await publishEvent(Event.AUTOMATION_TESTED, properties) await publishEvent(Event.AUTOMATION_TESTED, properties)
} }
export const run = async (count: number, timestamp?: string | number) => { const run = async (count: number, timestamp?: string | number) => {
const properties: AutomationsRunEvent = { const properties: AutomationsRunEvent = {
count, count,
} }
await publishEvent(Event.AUTOMATIONS_RUN, properties, timestamp) await publishEvent(Event.AUTOMATIONS_RUN, properties, timestamp)
} }
export async function stepCreated( async function stepCreated(
automation: Automation, automation: Automation,
step: AutomationStep, step: AutomationStep,
timestamp?: string | number timestamp?: string | number
@ -78,10 +75,7 @@ export async function stepCreated(
await publishEvent(Event.AUTOMATION_STEP_CREATED, properties, timestamp) await publishEvent(Event.AUTOMATION_STEP_CREATED, properties, timestamp)
} }
export async function stepDeleted( async function stepDeleted(automation: Automation, step: AutomationStep) {
automation: Automation,
step: AutomationStep
) {
const properties: AutomationStepDeletedEvent = { const properties: AutomationStepDeletedEvent = {
appId: automation.appId, appId: automation.appId,
automationId: automation._id as string, automationId: automation._id as string,
@ -92,3 +86,13 @@ export async function stepDeleted(
} }
await publishEvent(Event.AUTOMATION_STEP_DELETED, properties) await publishEvent(Event.AUTOMATION_STEP_DELETED, properties)
} }
export default {
created,
triggerUpdated,
deleted,
tested,
run,
stepCreated,
stepDeleted,
}

View File

@ -8,18 +8,18 @@ import {
InstallationBackfillSucceededEvent, InstallationBackfillSucceededEvent,
InstallationBackfillFailedEvent, InstallationBackfillFailedEvent,
} from "@budibase/types" } from "@budibase/types"
const env = require("../../environment") import env from "../../environment"
const shouldSkip = !env.SELF_HOSTED && !env.isDev() const shouldSkip = !env.SELF_HOSTED && !env.isDev()
export async function appSucceeded(properties: AppBackfillSucceededEvent) { async function appSucceeded(properties: AppBackfillSucceededEvent) {
if (shouldSkip) { if (shouldSkip) {
return return
} }
await publishEvent(Event.APP_BACKFILL_SUCCEEDED, properties) await publishEvent(Event.APP_BACKFILL_SUCCEEDED, properties)
} }
export async function appFailed(error: any) { async function appFailed(error: any) {
if (shouldSkip) { if (shouldSkip) {
return return
} }
@ -29,16 +29,14 @@ export async function appFailed(error: any) {
await publishEvent(Event.APP_BACKFILL_FAILED, properties) await publishEvent(Event.APP_BACKFILL_FAILED, properties)
} }
export async function tenantSucceeded( async function tenantSucceeded(properties: TenantBackfillSucceededEvent) {
properties: TenantBackfillSucceededEvent
) {
if (shouldSkip) { if (shouldSkip) {
return return
} }
await publishEvent(Event.TENANT_BACKFILL_SUCCEEDED, properties) await publishEvent(Event.TENANT_BACKFILL_SUCCEEDED, properties)
} }
export async function tenantFailed(error: any) { async function tenantFailed(error: any) {
if (shouldSkip) { if (shouldSkip) {
return return
} }
@ -48,7 +46,7 @@ export async function tenantFailed(error: any) {
await publishEvent(Event.TENANT_BACKFILL_FAILED, properties) await publishEvent(Event.TENANT_BACKFILL_FAILED, properties)
} }
export async function installationSucceeded() { async function installationSucceeded() {
if (shouldSkip) { if (shouldSkip) {
return return
} }
@ -56,7 +54,7 @@ export async function installationSucceeded() {
await publishEvent(Event.INSTALLATION_BACKFILL_SUCCEEDED, properties) await publishEvent(Event.INSTALLATION_BACKFILL_SUCCEEDED, properties)
} }
export async function installationFailed(error: any) { async function installationFailed(error: any) {
if (shouldSkip) { if (shouldSkip) {
return return
} }
@ -65,3 +63,12 @@ export async function installationFailed(error: any) {
} }
await publishEvent(Event.INSTALLATION_BACKFILL_FAILED, properties) await publishEvent(Event.INSTALLATION_BACKFILL_FAILED, properties)
} }
export default {
appSucceeded,
appFailed,
tenantSucceeded,
tenantFailed,
installationSucceeded,
installationFailed,
}

View File

@ -8,7 +8,7 @@ import {
} from "@budibase/types" } from "@budibase/types"
import { publishEvent } from "../events" import { publishEvent } from "../events"
export async function appBackupRestored(backup: AppBackup) { async function appBackupRestored(backup: AppBackup) {
const properties: AppBackupRestoreEvent = { const properties: AppBackupRestoreEvent = {
appId: backup.appId, appId: backup.appId,
restoreId: backup._id!, restoreId: backup._id!,
@ -18,7 +18,7 @@ export async function appBackupRestored(backup: AppBackup) {
await publishEvent(Event.APP_BACKUP_RESTORED, properties) await publishEvent(Event.APP_BACKUP_RESTORED, properties)
} }
export async function appBackupTriggered( async function appBackupTriggered(
appId: string, appId: string,
backupId: string, backupId: string,
type: AppBackupType, type: AppBackupType,
@ -32,3 +32,8 @@ export async function appBackupTriggered(
} }
await publishEvent(Event.APP_BACKUP_TRIGGERED, properties) await publishEvent(Event.APP_BACKUP_TRIGGERED, properties)
} }
export default {
appBackupRestored,
appBackupTriggered,
}

View File

@ -14,10 +14,7 @@ function isCustom(datasource: Datasource) {
return !sources.includes(datasource.source) return !sources.includes(datasource.source)
} }
export async function created( async function created(datasource: Datasource, timestamp?: string | number) {
datasource: Datasource,
timestamp?: string | number
) {
const properties: DatasourceCreatedEvent = { const properties: DatasourceCreatedEvent = {
datasourceId: datasource._id as string, datasourceId: datasource._id as string,
source: datasource.source, source: datasource.source,
@ -26,7 +23,7 @@ export async function created(
await publishEvent(Event.DATASOURCE_CREATED, properties, timestamp) await publishEvent(Event.DATASOURCE_CREATED, properties, timestamp)
} }
export async function updated(datasource: Datasource) { async function updated(datasource: Datasource) {
const properties: DatasourceUpdatedEvent = { const properties: DatasourceUpdatedEvent = {
datasourceId: datasource._id as string, datasourceId: datasource._id as string,
source: datasource.source, source: datasource.source,
@ -35,7 +32,7 @@ export async function updated(datasource: Datasource) {
await publishEvent(Event.DATASOURCE_UPDATED, properties) await publishEvent(Event.DATASOURCE_UPDATED, properties)
} }
export async function deleted(datasource: Datasource) { async function deleted(datasource: Datasource) {
const properties: DatasourceDeletedEvent = { const properties: DatasourceDeletedEvent = {
datasourceId: datasource._id as string, datasourceId: datasource._id as string,
source: datasource.source, source: datasource.source,
@ -43,3 +40,9 @@ export async function deleted(datasource: Datasource) {
} }
await publishEvent(Event.DATASOURCE_DELETED, properties) await publishEvent(Event.DATASOURCE_DELETED, properties)
} }
export default {
created,
updated,
deleted,
}

View File

@ -1,12 +1,17 @@
import { publishEvent } from "../events" import { publishEvent } from "../events"
import { Event, SMTPCreatedEvent, SMTPUpdatedEvent } from "@budibase/types" import { Event, SMTPCreatedEvent, SMTPUpdatedEvent } from "@budibase/types"
export async function SMTPCreated(timestamp?: string | number) { async function SMTPCreated(timestamp?: string | number) {
const properties: SMTPCreatedEvent = {} const properties: SMTPCreatedEvent = {}
await publishEvent(Event.EMAIL_SMTP_CREATED, properties, timestamp) await publishEvent(Event.EMAIL_SMTP_CREATED, properties, timestamp)
} }
export async function SMTPUpdated() { async function SMTPUpdated() {
const properties: SMTPUpdatedEvent = {} const properties: SMTPUpdatedEvent = {}
await publishEvent(Event.EMAIL_SMTP_UPDATED, properties) await publishEvent(Event.EMAIL_SMTP_UPDATED, properties)
} }
export default {
SMTPCreated,
SMTPUpdated,
}

View File

@ -0,0 +1,38 @@
import {
Event,
EnvironmentVariableCreatedEvent,
EnvironmentVariableDeletedEvent,
EnvironmentVariableUpgradePanelOpenedEvent,
} from "@budibase/types"
import { publishEvent } from "../events"
async function created(name: string, environments: string[]) {
const properties: EnvironmentVariableCreatedEvent = {
name,
environments,
}
await publishEvent(Event.ENVIRONMENT_VARIABLE_CREATED, properties)
}
async function deleted(name: string) {
const properties: EnvironmentVariableDeletedEvent = {
name,
}
await publishEvent(Event.ENVIRONMENT_VARIABLE_DELETED, properties)
}
async function upgradePanelOpened(userId: string) {
const properties: EnvironmentVariableUpgradePanelOpenedEvent = {
userId,
}
await publishEvent(
Event.ENVIRONMENT_VARIABLE_UPGRADE_PANEL_OPENED,
properties
)
}
export default {
created,
deleted,
upgradePanelOpened,
}

View File

@ -11,28 +11,28 @@ import {
UserGroupRoles, UserGroupRoles,
} from "@budibase/types" } from "@budibase/types"
export async function created(group: UserGroup, timestamp?: number) { async function created(group: UserGroup, timestamp?: number) {
const properties: GroupCreatedEvent = { const properties: GroupCreatedEvent = {
groupId: group._id as string, groupId: group._id as string,
} }
await publishEvent(Event.USER_GROUP_CREATED, properties, timestamp) await publishEvent(Event.USER_GROUP_CREATED, properties, timestamp)
} }
export async function updated(group: UserGroup) { async function updated(group: UserGroup) {
const properties: GroupUpdatedEvent = { const properties: GroupUpdatedEvent = {
groupId: group._id as string, groupId: group._id as string,
} }
await publishEvent(Event.USER_GROUP_UPDATED, properties) await publishEvent(Event.USER_GROUP_UPDATED, properties)
} }
export async function deleted(group: UserGroup) { async function deleted(group: UserGroup) {
const properties: GroupDeletedEvent = { const properties: GroupDeletedEvent = {
groupId: group._id as string, groupId: group._id as string,
} }
await publishEvent(Event.USER_GROUP_DELETED, properties) await publishEvent(Event.USER_GROUP_DELETED, properties)
} }
export async function usersAdded(count: number, group: UserGroup) { async function usersAdded(count: number, group: UserGroup) {
const properties: GroupUsersAddedEvent = { const properties: GroupUsersAddedEvent = {
count, count,
groupId: group._id as string, groupId: group._id as string,
@ -40,7 +40,7 @@ export async function usersAdded(count: number, group: UserGroup) {
await publishEvent(Event.USER_GROUP_USERS_ADDED, properties) await publishEvent(Event.USER_GROUP_USERS_ADDED, properties)
} }
export async function usersDeleted(count: number, group: UserGroup) { async function usersDeleted(count: number, group: UserGroup) {
const properties: GroupUsersDeletedEvent = { const properties: GroupUsersDeletedEvent = {
count, count,
groupId: group._id as string, groupId: group._id as string,
@ -48,7 +48,7 @@ export async function usersDeleted(count: number, group: UserGroup) {
await publishEvent(Event.USER_GROUP_USERS_REMOVED, properties) await publishEvent(Event.USER_GROUP_USERS_REMOVED, properties)
} }
export async function createdOnboarding(groupId: string) { async function createdOnboarding(groupId: string) {
const properties: GroupAddedOnboardingEvent = { const properties: GroupAddedOnboardingEvent = {
groupId: groupId, groupId: groupId,
onboarding: true, onboarding: true,
@ -56,9 +56,19 @@ export async function createdOnboarding(groupId: string) {
await publishEvent(Event.USER_GROUP_ONBOARDING, properties) await publishEvent(Event.USER_GROUP_ONBOARDING, properties)
} }
export async function permissionsEdited(roles: UserGroupRoles) { async function permissionsEdited(roles: UserGroupRoles) {
const properties: UserGroupRoles = { const properties: UserGroupRoles = {
...roles, ...roles,
} }
await publishEvent(Event.USER_GROUP_PERMISSIONS_EDITED, properties) await publishEvent(Event.USER_GROUP_PERMISSIONS_EDITED, properties)
} }
export default {
created,
updated,
deleted,
usersAdded,
usersDeleted,
createdOnboarding,
permissionsEdited,
}

View File

@ -1,22 +1,23 @@
export * as account from "./account" export { default as account } from "./account"
export * as app from "./app" export { default as app } from "./app"
export * as auth from "./auth" export { default as auth } from "./auth"
export * as automation from "./automation" export { default as automation } from "./automation"
export * as datasource from "./datasource" export { default as datasource } from "./datasource"
export * as email from "./email" export { default as email } from "./email"
export * as license from "./license" export { default as license } from "./license"
export * as layout from "./layout" export { default as layout } from "./layout"
export * as org from "./org" export { default as org } from "./org"
export * as query from "./query" export { default as query } from "./query"
export * as role from "./role" export { default as role } from "./role"
export * as screen from "./screen" export { default as screen } from "./screen"
export * as rows from "./rows" export { default as rows } from "./rows"
export * as table from "./table" export { default as table } from "./table"
export * as serve from "./serve" export { default as serve } from "./serve"
export * as user from "./user" export { default as user } from "./user"
export * as view from "./view" export { default as view } from "./view"
export * as installation from "./installation" export { default as installation } from "./installation"
export * as backfill from "./backfill" export { default as backfill } from "./backfill"
export * as group from "./group" export { default as group } from "./group"
export * as plugin from "./plugin" export { default as plugin } from "./plugin"
export * as backup from "./backup" export { default as backup } from "./backup"
export { default as environmentVariable } from "./environmentVariable"

View File

@ -1,14 +1,14 @@
import { publishEvent } from "../events" import { publishEvent } from "../events"
import { Event, VersionCheckedEvent, VersionChangeEvent } from "@budibase/types" import { Event, VersionCheckedEvent, VersionChangeEvent } from "@budibase/types"
export async function versionChecked(version: string) { async function versionChecked(version: string) {
const properties: VersionCheckedEvent = { const properties: VersionCheckedEvent = {
currentVersion: version, currentVersion: version,
} }
await publishEvent(Event.INSTALLATION_VERSION_CHECKED, properties) await publishEvent(Event.INSTALLATION_VERSION_CHECKED, properties)
} }
export async function upgraded(from: string, to: string) { async function upgraded(from: string, to: string) {
const properties: VersionChangeEvent = { const properties: VersionChangeEvent = {
from, from,
to, to,
@ -17,7 +17,7 @@ export async function upgraded(from: string, to: string) {
await publishEvent(Event.INSTALLATION_VERSION_UPGRADED, properties) await publishEvent(Event.INSTALLATION_VERSION_UPGRADED, properties)
} }
export async function downgraded(from: string, to: string) { async function downgraded(from: string, to: string) {
const properties: VersionChangeEvent = { const properties: VersionChangeEvent = {
from, from,
to, to,
@ -25,7 +25,14 @@ export async function downgraded(from: string, to: string) {
await publishEvent(Event.INSTALLATION_VERSION_DOWNGRADED, properties) await publishEvent(Event.INSTALLATION_VERSION_DOWNGRADED, properties)
} }
export async function firstStartup() { async function firstStartup() {
const properties = {} const properties = {}
await publishEvent(Event.INSTALLATION_FIRST_STARTUP, properties) await publishEvent(Event.INSTALLATION_FIRST_STARTUP, properties)
} }
export default {
versionChecked,
upgraded,
downgraded,
firstStartup,
}

View File

@ -6,16 +6,21 @@ import {
LayoutDeletedEvent, LayoutDeletedEvent,
} from "@budibase/types" } from "@budibase/types"
export async function created(layout: Layout, timestamp?: string | number) { async function created(layout: Layout, timestamp?: string | number) {
const properties: LayoutCreatedEvent = { const properties: LayoutCreatedEvent = {
layoutId: layout._id as string, layoutId: layout._id as string,
} }
await publishEvent(Event.LAYOUT_CREATED, properties, timestamp) await publishEvent(Event.LAYOUT_CREATED, properties, timestamp)
} }
export async function deleted(layoutId: string) { async function deleted(layoutId: string) {
const properties: LayoutDeletedEvent = { const properties: LayoutDeletedEvent = {
layoutId, layoutId,
} }
await publishEvent(Event.LAYOUT_DELETED, properties) await publishEvent(Event.LAYOUT_DELETED, properties)
} }
export default {
created,
deleted,
}

View File

@ -13,7 +13,7 @@ import {
LicensePaymentRecoveredEvent, LicensePaymentRecoveredEvent,
} from "@budibase/types" } from "@budibase/types"
export async function tierChanged(account: Account, from: number, to: number) { async function tierChanged(account: Account, from: number, to: number) {
const properties: LicenseTierChangedEvent = { const properties: LicenseTierChangedEvent = {
accountId: account.accountId, accountId: account.accountId,
to, to,
@ -22,11 +22,7 @@ export async function tierChanged(account: Account, from: number, to: number) {
await publishEvent(Event.LICENSE_TIER_CHANGED, properties) await publishEvent(Event.LICENSE_TIER_CHANGED, properties)
} }
export async function planChanged( async function planChanged(account: Account, from: PlanType, to: PlanType) {
account: Account,
from: PlanType,
to: PlanType
) {
const properties: LicensePlanChangedEvent = { const properties: LicensePlanChangedEvent = {
accountId: account.accountId, accountId: account.accountId,
to, to,
@ -35,44 +31,55 @@ export async function planChanged(
await publishEvent(Event.LICENSE_PLAN_CHANGED, properties) await publishEvent(Event.LICENSE_PLAN_CHANGED, properties)
} }
export async function activated(account: Account) { async function activated(account: Account) {
const properties: LicenseActivatedEvent = { const properties: LicenseActivatedEvent = {
accountId: account.accountId, accountId: account.accountId,
} }
await publishEvent(Event.LICENSE_ACTIVATED, properties) await publishEvent(Event.LICENSE_ACTIVATED, properties)
} }
export async function checkoutOpened(account: Account) { async function checkoutOpened(account: Account) {
const properties: LicenseCheckoutOpenedEvent = { const properties: LicenseCheckoutOpenedEvent = {
accountId: account.accountId, accountId: account.accountId,
} }
await publishEvent(Event.LICENSE_CHECKOUT_OPENED, properties) await publishEvent(Event.LICENSE_CHECKOUT_OPENED, properties)
} }
export async function checkoutSuccess(account: Account) { async function checkoutSuccess(account: Account) {
const properties: LicenseCheckoutSuccessEvent = { const properties: LicenseCheckoutSuccessEvent = {
accountId: account.accountId, accountId: account.accountId,
} }
await publishEvent(Event.LICENSE_CHECKOUT_SUCCESS, properties) await publishEvent(Event.LICENSE_CHECKOUT_SUCCESS, properties)
} }
export async function portalOpened(account: Account) { async function portalOpened(account: Account) {
const properties: LicensePortalOpenedEvent = { const properties: LicensePortalOpenedEvent = {
accountId: account.accountId, accountId: account.accountId,
} }
await publishEvent(Event.LICENSE_PORTAL_OPENED, properties) await publishEvent(Event.LICENSE_PORTAL_OPENED, properties)
} }
export async function paymentFailed(account: Account) { async function paymentFailed(account: Account) {
const properties: LicensePaymentFailedEvent = { const properties: LicensePaymentFailedEvent = {
accountId: account.accountId, accountId: account.accountId,
} }
await publishEvent(Event.LICENSE_PAYMENT_FAILED, properties) await publishEvent(Event.LICENSE_PAYMENT_FAILED, properties)
} }
export async function paymentRecovered(account: Account) { async function paymentRecovered(account: Account) {
const properties: LicensePaymentRecoveredEvent = { const properties: LicensePaymentRecoveredEvent = {
accountId: account.accountId, accountId: account.accountId,
} }
await publishEvent(Event.LICENSE_PAYMENT_RECOVERED, properties) await publishEvent(Event.LICENSE_PAYMENT_RECOVERED, properties)
} }
export default {
tierChanged,
planChanged,
activated,
checkoutOpened,
checkoutSuccess,
portalOpened,
paymentFailed,
paymentRecovered,
}

View File

@ -1,29 +1,37 @@
import { publishEvent } from "../events" import { publishEvent } from "../events"
import { Event } from "@budibase/types" import { Event } from "@budibase/types"
export async function nameUpdated(timestamp?: string | number) { async function nameUpdated(timestamp?: string | number) {
const properties = {} const properties = {}
await publishEvent(Event.ORG_NAME_UPDATED, properties, timestamp) await publishEvent(Event.ORG_NAME_UPDATED, properties, timestamp)
} }
export async function logoUpdated(timestamp?: string | number) { async function logoUpdated(timestamp?: string | number) {
const properties = {} const properties = {}
await publishEvent(Event.ORG_LOGO_UPDATED, properties, timestamp) await publishEvent(Event.ORG_LOGO_UPDATED, properties, timestamp)
} }
export async function platformURLUpdated(timestamp?: string | number) { async function platformURLUpdated(timestamp?: string | number) {
const properties = {} const properties = {}
await publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties, timestamp) await publishEvent(Event.ORG_PLATFORM_URL_UPDATED, properties, timestamp)
} }
// TODO // TODO
export async function analyticsOptOut() { async function analyticsOptOut() {
const properties = {} const properties = {}
await publishEvent(Event.ANALYTICS_OPT_OUT, properties) await publishEvent(Event.ANALYTICS_OPT_OUT, properties)
} }
export async function analyticsOptIn() { async function analyticsOptIn() {
const properties = {} const properties = {}
await publishEvent(Event.ANALYTICS_OPT_OUT, properties) await publishEvent(Event.ANALYTICS_OPT_OUT, properties)
} }
export default {
nameUpdated,
logoUpdated,
platformURLUpdated,
analyticsOptOut,
analyticsOptIn,
}

View File

@ -7,7 +7,7 @@ import {
PluginInitEvent, PluginInitEvent,
} from "@budibase/types" } from "@budibase/types"
export async function init(plugin: Plugin) { async function init(plugin: Plugin) {
const properties: PluginInitEvent = { const properties: PluginInitEvent = {
type: plugin.schema.type, type: plugin.schema.type,
name: plugin.name, name: plugin.name,
@ -17,7 +17,7 @@ export async function init(plugin: Plugin) {
await publishEvent(Event.PLUGIN_INIT, properties) await publishEvent(Event.PLUGIN_INIT, properties)
} }
export async function imported(plugin: Plugin) { async function imported(plugin: Plugin) {
const properties: PluginImportedEvent = { const properties: PluginImportedEvent = {
pluginId: plugin._id as string, pluginId: plugin._id as string,
type: plugin.schema.type, type: plugin.schema.type,
@ -29,7 +29,7 @@ export async function imported(plugin: Plugin) {
await publishEvent(Event.PLUGIN_IMPORTED, properties) await publishEvent(Event.PLUGIN_IMPORTED, properties)
} }
export async function deleted(plugin: Plugin) { async function deleted(plugin: Plugin) {
const properties: PluginDeletedEvent = { const properties: PluginDeletedEvent = {
pluginId: plugin._id as string, pluginId: plugin._id as string,
type: plugin.schema.type, type: plugin.schema.type,
@ -39,3 +39,9 @@ export async function deleted(plugin: Plugin) {
} }
await publishEvent(Event.PLUGIN_DELETED, properties) await publishEvent(Event.PLUGIN_DELETED, properties)
} }
export default {
init,
imported,
deleted,
}

View File

@ -13,7 +13,7 @@ import {
/* eslint-disable */ /* eslint-disable */
export const created = async ( const created = async (
datasource: Datasource, datasource: Datasource,
query: Query, query: Query,
timestamp?: string | number timestamp?: string | number
@ -27,7 +27,7 @@ export const created = async (
await publishEvent(Event.QUERY_CREATED, properties, timestamp) await publishEvent(Event.QUERY_CREATED, properties, timestamp)
} }
export const updated = async (datasource: Datasource, query: Query) => { const updated = async (datasource: Datasource, query: Query) => {
const properties: QueryUpdatedEvent = { const properties: QueryUpdatedEvent = {
queryId: query._id as string, queryId: query._id as string,
datasourceId: datasource._id as string, datasourceId: datasource._id as string,
@ -37,7 +37,7 @@ export const updated = async (datasource: Datasource, query: Query) => {
await publishEvent(Event.QUERY_UPDATED, properties) await publishEvent(Event.QUERY_UPDATED, properties)
} }
export const deleted = async (datasource: Datasource, query: Query) => { const deleted = async (datasource: Datasource, query: Query) => {
const properties: QueryDeletedEvent = { const properties: QueryDeletedEvent = {
queryId: query._id as string, queryId: query._id as string,
datasourceId: datasource._id as string, datasourceId: datasource._id as string,
@ -47,7 +47,7 @@ export const deleted = async (datasource: Datasource, query: Query) => {
await publishEvent(Event.QUERY_DELETED, properties) await publishEvent(Event.QUERY_DELETED, properties)
} }
export const imported = async ( const imported = async (
datasource: Datasource, datasource: Datasource,
importSource: any, importSource: any,
count: any count: any
@ -61,14 +61,14 @@ export const imported = async (
await publishEvent(Event.QUERY_IMPORT, properties) await publishEvent(Event.QUERY_IMPORT, properties)
} }
export const run = async (count: number, timestamp?: string | number) => { const run = async (count: number, timestamp?: string | number) => {
const properties: QueriesRunEvent = { const properties: QueriesRunEvent = {
count, count,
} }
await publishEvent(Event.QUERIES_RUN, properties, timestamp) await publishEvent(Event.QUERIES_RUN, properties, timestamp)
} }
export const previewed = async (datasource: Datasource, query: Query) => { const previewed = async (datasource: Datasource, query: Query) => {
const properties: QueryPreviewedEvent = { const properties: QueryPreviewedEvent = {
queryId: query._id, queryId: query._id,
datasourceId: datasource._id as string, datasourceId: datasource._id as string,
@ -77,3 +77,12 @@ export const previewed = async (datasource: Datasource, query: Query) => {
} }
await publishEvent(Event.QUERY_PREVIEWED, properties) await publishEvent(Event.QUERY_PREVIEWED, properties)
} }
export default {
created,
updated,
deleted,
imported,
run,
previewed,
}

View File

@ -10,7 +10,7 @@ import {
User, User,
} from "@budibase/types" } from "@budibase/types"
export async function created(role: Role, timestamp?: string | number) { async function created(role: Role, timestamp?: string | number) {
const properties: RoleCreatedEvent = { const properties: RoleCreatedEvent = {
roleId: role._id as string, roleId: role._id as string,
permissionId: role.permissionId, permissionId: role.permissionId,
@ -19,7 +19,7 @@ export async function created(role: Role, timestamp?: string | number) {
await publishEvent(Event.ROLE_CREATED, properties, timestamp) await publishEvent(Event.ROLE_CREATED, properties, timestamp)
} }
export async function updated(role: Role) { async function updated(role: Role) {
const properties: RoleUpdatedEvent = { const properties: RoleUpdatedEvent = {
roleId: role._id as string, roleId: role._id as string,
permissionId: role.permissionId, permissionId: role.permissionId,
@ -28,7 +28,7 @@ export async function updated(role: Role) {
await publishEvent(Event.ROLE_UPDATED, properties) await publishEvent(Event.ROLE_UPDATED, properties)
} }
export async function deleted(role: Role) { async function deleted(role: Role) {
const properties: RoleDeletedEvent = { const properties: RoleDeletedEvent = {
roleId: role._id as string, roleId: role._id as string,
permissionId: role.permissionId, permissionId: role.permissionId,
@ -37,7 +37,7 @@ export async function deleted(role: Role) {
await publishEvent(Event.ROLE_DELETED, properties) await publishEvent(Event.ROLE_DELETED, properties)
} }
export async function assigned(user: User, roleId: string, timestamp?: number) { async function assigned(user: User, roleId: string, timestamp?: number) {
const properties: RoleAssignedEvent = { const properties: RoleAssignedEvent = {
userId: user._id as string, userId: user._id as string,
roleId, roleId,
@ -45,10 +45,18 @@ export async function assigned(user: User, roleId: string, timestamp?: number) {
await publishEvent(Event.ROLE_ASSIGNED, properties, timestamp) await publishEvent(Event.ROLE_ASSIGNED, properties, timestamp)
} }
export async function unassigned(user: User, roleId: string) { async function unassigned(user: User, roleId: string) {
const properties: RoleUnassignedEvent = { const properties: RoleUnassignedEvent = {
userId: user._id as string, userId: user._id as string,
roleId, roleId,
} }
await publishEvent(Event.ROLE_UNASSIGNED, properties) await publishEvent(Event.ROLE_UNASSIGNED, properties)
} }
export default {
created,
updated,
deleted,
assigned,
unassigned,
}

View File

@ -3,28 +3,27 @@ import {
Event, Event,
RowsImportedEvent, RowsImportedEvent,
RowsCreatedEvent, RowsCreatedEvent,
RowImportFormat,
Table, Table,
} from "@budibase/types" } from "@budibase/types"
/* eslint-disable */ /* eslint-disable */
export const created = async (count: number, timestamp?: string | number) => { const created = async (count: number, timestamp?: string | number) => {
const properties: RowsCreatedEvent = { const properties: RowsCreatedEvent = {
count, count,
} }
await publishEvent(Event.ROWS_CREATED, properties, timestamp) await publishEvent(Event.ROWS_CREATED, properties, timestamp)
} }
export const imported = async ( const imported = async (table: Table, count: number) => {
table: Table,
format: RowImportFormat,
count: number
) => {
const properties: RowsImportedEvent = { const properties: RowsImportedEvent = {
tableId: table._id as string, tableId: table._id as string,
format,
count, count,
} }
await publishEvent(Event.ROWS_IMPORTED, properties) await publishEvent(Event.ROWS_IMPORTED, properties)
} }
export default {
created,
imported,
}

View File

@ -6,7 +6,7 @@ import {
ScreenDeletedEvent, ScreenDeletedEvent,
} from "@budibase/types" } from "@budibase/types"
export async function created(screen: Screen, timestamp?: string | number) { async function created(screen: Screen, timestamp?: string | number) {
const properties: ScreenCreatedEvent = { const properties: ScreenCreatedEvent = {
layoutId: screen.layoutId, layoutId: screen.layoutId,
screenId: screen._id as string, screenId: screen._id as string,
@ -15,7 +15,7 @@ export async function created(screen: Screen, timestamp?: string | number) {
await publishEvent(Event.SCREEN_CREATED, properties, timestamp) await publishEvent(Event.SCREEN_CREATED, properties, timestamp)
} }
export async function deleted(screen: Screen) { async function deleted(screen: Screen) {
const properties: ScreenDeletedEvent = { const properties: ScreenDeletedEvent = {
layoutId: screen.layoutId, layoutId: screen.layoutId,
screenId: screen._id as string, screenId: screen._id as string,
@ -23,3 +23,8 @@ export async function deleted(screen: Screen) {
} }
await publishEvent(Event.SCREEN_DELETED, properties) await publishEvent(Event.SCREEN_DELETED, properties)
} }
export default {
created,
deleted,
}

View File

@ -7,14 +7,14 @@ import {
AppServedEvent, AppServedEvent,
} from "@budibase/types" } from "@budibase/types"
export async function servedBuilder(timezone: string) { async function servedBuilder(timezone: string) {
const properties: BuilderServedEvent = { const properties: BuilderServedEvent = {
timezone, timezone,
} }
await publishEvent(Event.SERVED_BUILDER, properties) await publishEvent(Event.SERVED_BUILDER, properties)
} }
export async function servedApp(app: App, timezone: string) { async function servedApp(app: App, timezone: string) {
const properties: AppServedEvent = { const properties: AppServedEvent = {
appVersion: app.version, appVersion: app.version,
timezone, timezone,
@ -22,7 +22,7 @@ export async function servedApp(app: App, timezone: string) {
await publishEvent(Event.SERVED_APP, properties) await publishEvent(Event.SERVED_APP, properties)
} }
export async function servedAppPreview(app: App, timezone: string) { async function servedAppPreview(app: App, timezone: string) {
const properties: AppPreviewServedEvent = { const properties: AppPreviewServedEvent = {
appId: app.appId, appId: app.appId,
appVersion: app.version, appVersion: app.version,
@ -30,3 +30,9 @@ export async function servedAppPreview(app: App, timezone: string) {
} }
await publishEvent(Event.SERVED_APP_PREVIEW, properties) await publishEvent(Event.SERVED_APP_PREVIEW, properties)
} }
export default {
servedBuilder,
servedApp,
servedAppPreview,
}

View File

@ -2,7 +2,6 @@ import { publishEvent } from "../events"
import { import {
Event, Event,
TableExportFormat, TableExportFormat,
TableImportFormat,
Table, Table,
TableCreatedEvent, TableCreatedEvent,
TableUpdatedEvent, TableUpdatedEvent,
@ -11,28 +10,28 @@ import {
TableImportedEvent, TableImportedEvent,
} from "@budibase/types" } from "@budibase/types"
export async function created(table: Table, timestamp?: string | number) { async function created(table: Table, timestamp?: string | number) {
const properties: TableCreatedEvent = { const properties: TableCreatedEvent = {
tableId: table._id as string, tableId: table._id as string,
} }
await publishEvent(Event.TABLE_CREATED, properties, timestamp) await publishEvent(Event.TABLE_CREATED, properties, timestamp)
} }
export async function updated(table: Table) { async function updated(table: Table) {
const properties: TableUpdatedEvent = { const properties: TableUpdatedEvent = {
tableId: table._id as string, tableId: table._id as string,
} }
await publishEvent(Event.TABLE_UPDATED, properties) await publishEvent(Event.TABLE_UPDATED, properties)
} }
export async function deleted(table: Table) { async function deleted(table: Table) {
const properties: TableDeletedEvent = { const properties: TableDeletedEvent = {
tableId: table._id as string, tableId: table._id as string,
} }
await publishEvent(Event.TABLE_DELETED, properties) await publishEvent(Event.TABLE_DELETED, properties)
} }
export async function exported(table: Table, format: TableExportFormat) { async function exported(table: Table, format: TableExportFormat) {
const properties: TableExportedEvent = { const properties: TableExportedEvent = {
tableId: table._id as string, tableId: table._id as string,
format, format,
@ -40,10 +39,17 @@ export async function exported(table: Table, format: TableExportFormat) {
await publishEvent(Event.TABLE_EXPORTED, properties) await publishEvent(Event.TABLE_EXPORTED, properties)
} }
export async function imported(table: Table, format: TableImportFormat) { async function imported(table: Table) {
const properties: TableImportedEvent = { const properties: TableImportedEvent = {
tableId: table._id as string, tableId: table._id as string,
format,
} }
await publishEvent(Event.TABLE_IMPORTED, properties) await publishEvent(Event.TABLE_IMPORTED, properties)
} }
export default {
created,
updated,
deleted,
exported,
imported,
}

View File

@ -13,32 +13,40 @@ import {
UserPermissionAssignedEvent, UserPermissionAssignedEvent,
UserPermissionRemovedEvent, UserPermissionRemovedEvent,
UserUpdatedEvent, UserUpdatedEvent,
UserOnboardingEvent,
} from "@budibase/types" } from "@budibase/types"
export async function created(user: User, timestamp?: number) { async function created(user: User, timestamp?: number) {
const properties: UserCreatedEvent = { const properties: UserCreatedEvent = {
userId: user._id as string, userId: user._id as string,
} }
await publishEvent(Event.USER_CREATED, properties, timestamp) await publishEvent(Event.USER_CREATED, properties, timestamp)
} }
export async function updated(user: User) { async function updated(user: User) {
const properties: UserUpdatedEvent = { const properties: UserUpdatedEvent = {
userId: user._id as string, userId: user._id as string,
} }
await publishEvent(Event.USER_UPDATED, properties) await publishEvent(Event.USER_UPDATED, properties)
} }
export async function deleted(user: User) { async function deleted(user: User) {
const properties: UserDeletedEvent = { const properties: UserDeletedEvent = {
userId: user._id as string, userId: user._id as string,
} }
await publishEvent(Event.USER_DELETED, properties) await publishEvent(Event.USER_DELETED, properties)
} }
export async function onboardingComplete(user: User) {
const properties: UserOnboardingEvent = {
userId: user._id as string,
}
await publishEvent(Event.USER_ONBOARDING_COMPLETE, properties)
}
// PERMISSIONS // PERMISSIONS
export async function permissionAdminAssigned(user: User, timestamp?: number) { async function permissionAdminAssigned(user: User, timestamp?: number) {
const properties: UserPermissionAssignedEvent = { const properties: UserPermissionAssignedEvent = {
userId: user._id as string, userId: user._id as string,
} }
@ -49,17 +57,14 @@ export async function permissionAdminAssigned(user: User, timestamp?: number) {
) )
} }
export async function permissionAdminRemoved(user: User) { async function permissionAdminRemoved(user: User) {
const properties: UserPermissionRemovedEvent = { const properties: UserPermissionRemovedEvent = {
userId: user._id as string, userId: user._id as string,
} }
await publishEvent(Event.USER_PERMISSION_ADMIN_REMOVED, properties) await publishEvent(Event.USER_PERMISSION_ADMIN_REMOVED, properties)
} }
export async function permissionBuilderAssigned( async function permissionBuilderAssigned(user: User, timestamp?: number) {
user: User,
timestamp?: number
) {
const properties: UserPermissionAssignedEvent = { const properties: UserPermissionAssignedEvent = {
userId: user._id as string, userId: user._id as string,
} }
@ -70,7 +75,7 @@ export async function permissionBuilderAssigned(
) )
} }
export async function permissionBuilderRemoved(user: User) { async function permissionBuilderRemoved(user: User) {
const properties: UserPermissionRemovedEvent = { const properties: UserPermissionRemovedEvent = {
userId: user._id as string, userId: user._id as string,
} }
@ -79,12 +84,12 @@ export async function permissionBuilderRemoved(user: User) {
// INVITE // INVITE
export async function invited() { async function invited() {
const properties: UserInvitedEvent = {} const properties: UserInvitedEvent = {}
await publishEvent(Event.USER_INVITED, properties) await publishEvent(Event.USER_INVITED, properties)
} }
export async function inviteAccepted(user: User) { async function inviteAccepted(user: User) {
const properties: UserInviteAcceptedEvent = { const properties: UserInviteAcceptedEvent = {
userId: user._id as string, userId: user._id as string,
} }
@ -93,30 +98,47 @@ export async function inviteAccepted(user: User) {
// PASSWORD // PASSWORD
export async function passwordForceReset(user: User) { async function passwordForceReset(user: User) {
const properties: UserPasswordForceResetEvent = { const properties: UserPasswordForceResetEvent = {
userId: user._id as string, userId: user._id as string,
} }
await publishEvent(Event.USER_PASSWORD_FORCE_RESET, properties) await publishEvent(Event.USER_PASSWORD_FORCE_RESET, properties)
} }
export async function passwordUpdated(user: User) { async function passwordUpdated(user: User) {
const properties: UserPasswordUpdatedEvent = { const properties: UserPasswordUpdatedEvent = {
userId: user._id as string, userId: user._id as string,
} }
await publishEvent(Event.USER_PASSWORD_UPDATED, properties) await publishEvent(Event.USER_PASSWORD_UPDATED, properties)
} }
export async function passwordResetRequested(user: User) { async function passwordResetRequested(user: User) {
const properties: UserPasswordResetRequestedEvent = { const properties: UserPasswordResetRequestedEvent = {
userId: user._id as string, userId: user._id as string,
} }
await publishEvent(Event.USER_PASSWORD_RESET_REQUESTED, properties) await publishEvent(Event.USER_PASSWORD_RESET_REQUESTED, properties)
} }
export async function passwordReset(user: User) { async function passwordReset(user: User) {
const properties: UserPasswordResetEvent = { const properties: UserPasswordResetEvent = {
userId: user._id as string, userId: user._id as string,
} }
await publishEvent(Event.USER_PASSWORD_RESET, properties) await publishEvent(Event.USER_PASSWORD_RESET, properties)
} }
export default {
created,
updated,
deleted,
permissionAdminAssigned,
permissionAdminRemoved,
permissionBuilderAssigned,
permissionBuilderRemoved,
onboardingComplete,
invited,
inviteAccepted,
passwordForceReset,
passwordUpdated,
passwordResetRequested,
passwordReset,
}

View File

@ -19,28 +19,28 @@ import {
/* eslint-disable */ /* eslint-disable */
export async function created(view: View, timestamp?: string | number) { async function created(view: View, timestamp?: string | number) {
const properties: ViewCreatedEvent = { const properties: ViewCreatedEvent = {
tableId: view.tableId, tableId: view.tableId,
} }
await publishEvent(Event.VIEW_CREATED, properties, timestamp) await publishEvent(Event.VIEW_CREATED, properties, timestamp)
} }
export async function updated(view: View) { async function updated(view: View) {
const properties: ViewUpdatedEvent = { const properties: ViewUpdatedEvent = {
tableId: view.tableId, tableId: view.tableId,
} }
await publishEvent(Event.VIEW_UPDATED, properties) await publishEvent(Event.VIEW_UPDATED, properties)
} }
export async function deleted(view: View) { async function deleted(view: View) {
const properties: ViewDeletedEvent = { const properties: ViewDeletedEvent = {
tableId: view.tableId, tableId: view.tableId,
} }
await publishEvent(Event.VIEW_DELETED, properties) await publishEvent(Event.VIEW_DELETED, properties)
} }
export async function exported(table: Table, format: TableExportFormat) { async function exported(table: Table, format: TableExportFormat) {
const properties: ViewExportedEvent = { const properties: ViewExportedEvent = {
tableId: table._id as string, tableId: table._id as string,
format, format,
@ -48,31 +48,28 @@ export async function exported(table: Table, format: TableExportFormat) {
await publishEvent(Event.VIEW_EXPORTED, properties) await publishEvent(Event.VIEW_EXPORTED, properties)
} }
export async function filterCreated(view: View, timestamp?: string | number) { async function filterCreated(view: View, timestamp?: string | number) {
const properties: ViewFilterCreatedEvent = { const properties: ViewFilterCreatedEvent = {
tableId: view.tableId, tableId: view.tableId,
} }
await publishEvent(Event.VIEW_FILTER_CREATED, properties, timestamp) await publishEvent(Event.VIEW_FILTER_CREATED, properties, timestamp)
} }
export async function filterUpdated(view: View) { async function filterUpdated(view: View) {
const properties: ViewFilterUpdatedEvent = { const properties: ViewFilterUpdatedEvent = {
tableId: view.tableId, tableId: view.tableId,
} }
await publishEvent(Event.VIEW_FILTER_UPDATED, properties) await publishEvent(Event.VIEW_FILTER_UPDATED, properties)
} }
export async function filterDeleted(view: View) { async function filterDeleted(view: View) {
const properties: ViewFilterDeletedEvent = { const properties: ViewFilterDeletedEvent = {
tableId: view.tableId, tableId: view.tableId,
} }
await publishEvent(Event.VIEW_FILTER_DELETED, properties) await publishEvent(Event.VIEW_FILTER_DELETED, properties)
} }
export async function calculationCreated( async function calculationCreated(view: View, timestamp?: string | number) {
view: View,
timestamp?: string | number
) {
const properties: ViewCalculationCreatedEvent = { const properties: ViewCalculationCreatedEvent = {
tableId: view.tableId, tableId: view.tableId,
calculation: view.calculation as ViewCalculation, calculation: view.calculation as ViewCalculation,
@ -80,7 +77,7 @@ export async function calculationCreated(
await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp) await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp)
} }
export async function calculationUpdated(view: View) { async function calculationUpdated(view: View) {
const properties: ViewCalculationUpdatedEvent = { const properties: ViewCalculationUpdatedEvent = {
tableId: view.tableId, tableId: view.tableId,
calculation: view.calculation as ViewCalculation, calculation: view.calculation as ViewCalculation,
@ -88,10 +85,23 @@ export async function calculationUpdated(view: View) {
await publishEvent(Event.VIEW_CALCULATION_UPDATED, properties) await publishEvent(Event.VIEW_CALCULATION_UPDATED, properties)
} }
export async function calculationDeleted(existingView: View) { async function calculationDeleted(existingView: View) {
const properties: ViewCalculationDeletedEvent = { const properties: ViewCalculationDeletedEvent = {
tableId: existingView.tableId, tableId: existingView.tableId,
calculation: existingView.calculation as ViewCalculation, calculation: existingView.calculation as ViewCalculation,
} }
await publishEvent(Event.VIEW_CALCULATION_DELETED, properties) await publishEvent(Event.VIEW_CALCULATION_DELETED, properties)
} }
export default {
created,
updated,
deleted,
exported,
filterCreated,
filterUpdated,
filterDeleted,
calculationCreated,
calculationUpdated,
calculationDeleted,
}

View File

@ -6,7 +6,7 @@ import * as tenancy from "../tenancy"
* The env var is formatted as: * The env var is formatted as:
* tenant1:feature1:feature2,tenant2:feature1 * tenant1:feature1:feature2,tenant2:feature1
*/ */
function getFeatureFlags() { export function buildFeatureFlags() {
if (!env.TENANT_FEATURE_FLAGS) { if (!env.TENANT_FEATURE_FLAGS) {
return return
} }
@ -27,8 +27,6 @@ function getFeatureFlags() {
return tenantFeatureFlags return tenantFeatureFlags
} }
const TENANT_FEATURE_FLAGS = getFeatureFlags()
export function isEnabled(featureFlag: string) { export function isEnabled(featureFlag: string) {
const tenantId = tenancy.getTenantId() const tenantId = tenancy.getTenantId()
const flags = getTenantFeatureFlags(tenantId) const flags = getTenantFeatureFlags(tenantId)
@ -36,18 +34,36 @@ export function isEnabled(featureFlag: string) {
} }
export function getTenantFeatureFlags(tenantId: string) { export function getTenantFeatureFlags(tenantId: string) {
const flags = [] let flags: string[] = []
const envFlags = buildFeatureFlags()
if (envFlags) {
const globalFlags = envFlags["*"]
const tenantFlags = envFlags[tenantId] || []
if (TENANT_FEATURE_FLAGS) { // Explicitly exclude tenants from global features if required.
const globalFlags = TENANT_FEATURE_FLAGS["*"] // Prefix the tenant flag with '!'
const tenantFlags = TENANT_FEATURE_FLAGS[tenantId] const tenantOverrides = tenantFlags.reduce(
(acc: string[], flag: string) => {
if (flag.startsWith("!")) {
let stripped = flag.substring(1)
acc.push(stripped)
}
return acc
},
[]
)
if (globalFlags) { if (globalFlags) {
flags.push(...globalFlags) flags.push(...globalFlags)
} }
if (tenantFlags) { if (tenantFlags.length) {
flags.push(...tenantFlags) flags.push(...tenantFlags)
} }
// Purge any tenant specific overrides
flags = flags.filter(flag => {
return tenantOverrides.indexOf(flag) == -1 && !flag.startsWith("!")
})
} }
return flags return flags
@ -57,4 +73,5 @@ export enum TenantFeatureFlag {
LICENSING = "LICENSING", LICENSING = "LICENSING",
GOOGLE_SHEETS = "GOOGLE_SHEETS", GOOGLE_SHEETS = "GOOGLE_SHEETS",
USER_GROUPS = "USER_GROUPS", USER_GROUPS = "USER_GROUPS",
ONBOARDING_TOUR = "ONBOARDING_TOUR",
} }

View File

@ -0,0 +1,85 @@
import {
TenantFeatureFlag,
buildFeatureFlags,
getTenantFeatureFlags,
} from "../"
import env from "../../environment"
const { ONBOARDING_TOUR, LICENSING, USER_GROUPS } = TenantFeatureFlag
describe("featureFlags", () => {
beforeEach(() => {
env._set("TENANT_FEATURE_FLAGS", "")
})
it("Should return no flags when the TENANT_FEATURE_FLAG is empty", async () => {
let features = buildFeatureFlags()
expect(features).toBeUndefined()
})
it("Should generate a map of global and named tenant feature flags from the env value", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR},tenant2:${USER_GROUPS},tenant1:${LICENSING}`
)
const parsedFlags: Record<string, string[]> = {
"*": [ONBOARDING_TOUR],
tenant1: [`!${ONBOARDING_TOUR}`, LICENSING],
tenant2: [USER_GROUPS],
}
let features = buildFeatureFlags()
expect(features).toBeDefined()
expect(features).toEqual(parsedFlags)
})
it("Should add feature flag flag only to explicitly configured tenant", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${LICENSING},*:${USER_GROUPS},tenant1:${ONBOARDING_TOUR}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([LICENSING, USER_GROUPS, ONBOARDING_TOUR])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING, USER_GROUPS])
})
})
it("Should exclude tenant1 from global feature flag", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${LICENSING},*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([LICENSING])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING, ONBOARDING_TOUR])
})
it("Should explicitly add flags to configured tenants only", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`tenant1:${ONBOARDING_TOUR},tenant1:${LICENSING},tenant2:${LICENSING}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([ONBOARDING_TOUR, LICENSING])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING])
})

View File

@ -1,68 +1,42 @@
import errors from "./errors" export * as events from "./events"
const errorClasses = errors.errors export * as migrations from "./migrations"
import * as events from "./events" export * as users from "./users"
import * as migrations from "./migrations" export * as roles from "./security/roles"
import * as users from "./users" export * as permissions from "./security/permissions"
import * as roles from "./security/roles" export * as accounts from "./cloud/accounts"
import * as permissions from "./security/permissions" export * as installation from "./installation"
import * as accounts from "./cloud/accounts" export * as tenancy from "./tenancy"
import * as installation from "./installation" export * as featureFlags from "./featureFlags"
import env from "./environment" export * as sessions from "./security/sessions"
import * as tenancy from "./tenancy" export * as deprovisioning from "./context/deprovision"
import * as featureFlags from "./featureFlags" export * as auth from "./auth"
import * as sessions from "./security/sessions" export * as constants from "./constants"
import * as deprovisioning from "./context/deprovision" export * as logging from "./logging"
import * as auth from "./auth" export * as middleware from "./middleware"
import * as constants from "./constants" export * as plugins from "./plugin"
import * as logging from "./logging" export * as encryption from "./security/encryption"
import * as pino from "./pino" export * as queue from "./queue"
import * as middleware from "./middleware" export * as db from "./db"
import * as plugins from "./plugin" export * as context from "./context"
import * as encryption from "./security/encryption" export * as cache from "./cache"
import * as queue from "./queue" export * as objectStore from "./objectStore"
import * as db from "./db" export * as redis from "./redis"
import * as context from "./context" export * as utils from "./utils"
import * as cache from "./cache" export * as errors from "./errors"
import * as objectStore from "./objectStore" export { default as env } from "./environment"
import * as redis from "./redis"
import * as utils from "./utils"
const init = (opts: any = {}) => { // expose error classes directly
export * from "./errors"
// expose constants directly
export * from "./constants"
// expose inner locks from redis directly
import * as redis from "./redis"
export const locks = redis.redlock
// expose package init function
import * as db from "./db"
export const init = (opts: any = {}) => {
db.init(opts.db) db.init(opts.db)
} }
const core = {
init,
db,
...constants,
redis,
locks: redis.redlock,
objectStore,
utils,
users,
cache,
auth,
constants,
migrations,
env,
accounts,
tenancy,
context,
featureFlags,
events,
sessions,
deprovisioning,
installation,
errors,
logging,
roles,
plugins,
...pino,
...errorClasses,
middleware,
encryption,
queue,
permissions,
}
export = core

View File

@ -1,3 +1,9 @@
import { Header } from "./constants"
import env from "./environment"
const correlator = require("correlation-id")
import { Options } from "pino-http"
import { IncomingMessage } from "http"
const NonErrors = ["AccountError"] const NonErrors = ["AccountError"]
function isSuppressed(e?: any) { function isSuppressed(e?: any) {
@ -29,8 +35,26 @@ export function logWarn(message: string) {
console.warn(`bb-warn: ${message}`) console.warn(`bb-warn: ${message}`)
} }
export default { export function pinoSettings(): Options {
logAlert, return {
logAlertWithInfo, prettyPrint: {
logWarn, levelFirst: true,
},
genReqId: correlator.getId,
level: env.LOG_LEVEL || "error",
autoLogging: {
ignore: (req: IncomingMessage) => !!req.url?.includes("/health"),
},
}
}
const setCorrelationHeader = (headers: any) => {
const correlationId = correlator.getId()
if (correlationId) {
headers[Header.CORRELATION_ID] = correlationId
}
}
export const correlation = {
setHeader: setCorrelationHeader,
} }

View File

@ -1,6 +1,6 @@
import { BBContext } from "@budibase/types" import { BBContext } from "@budibase/types"
export = async (ctx: BBContext, next: any) => { export default async (ctx: BBContext, next: any) => {
if ( if (
!ctx.internal && !ctx.internal &&
(!ctx.user || !ctx.user.admin || !ctx.user.admin.global) (!ctx.user || !ctx.user.admin || !ctx.user.admin.global)

View File

@ -1,6 +1,6 @@
import { BBContext } from "@budibase/types" import { BBContext } from "@budibase/types"
export = async (ctx: BBContext | any, next: any) => { export default async (ctx: BBContext | any, next: any) => {
// Placeholder for audit log middleware // Placeholder for audit log middleware
return next() return next()
} }

View File

@ -66,7 +66,7 @@ async function checkApiKey(apiKey: string, populateUser?: Function) {
* The tenancy modules should not be used here and it should be assumed that the tenancy context * The tenancy modules should not be used here and it should be assumed that the tenancy context
* has not yet been populated. * has not yet been populated.
*/ */
export = function ( export default function (
noAuthPatterns: EndpointMatcher[] = [], noAuthPatterns: EndpointMatcher[] = [],
opts: { publicAllowed?: boolean; populateUser?: Function } = { opts: { publicAllowed?: boolean; populateUser?: Function } = {
publicAllowed: false, publicAllowed: false,

View File

@ -1,6 +1,6 @@
import { BBContext } from "@budibase/types" import { BBContext } from "@budibase/types"
export = async (ctx: BBContext, next: any) => { export default async (ctx: BBContext, next: any) => {
if ( if (
!ctx.internal && !ctx.internal &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global) (!ctx.user || !ctx.user.builder || !ctx.user.builder.global)

View File

@ -1,6 +1,6 @@
import { BBContext } from "@budibase/types" import { BBContext } from "@budibase/types"
export = async (ctx: BBContext, next: any) => { export default async (ctx: BBContext, next: any) => {
if ( if (
!ctx.internal && !ctx.internal &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global) && (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) &&

View File

@ -32,7 +32,7 @@ const INCLUDED_CONTENT_TYPES = [
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern * https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern
* *
*/ */
export = function ( export default function (
opts: { noCsrfPatterns: EndpointMatcher[] } = { noCsrfPatterns: [] } opts: { noCsrfPatterns: EndpointMatcher[] } = { noCsrfPatterns: [] }
) { ) {
const noCsrfOptions = buildMatcherRegex(opts.noCsrfPatterns) const noCsrfOptions = buildMatcherRegex(opts.noCsrfPatterns)

View File

@ -1,38 +1,19 @@
import * as jwt from "./passport/jwt" export * as jwt from "./passport/jwt"
import * as local from "./passport/local" export * as local from "./passport/local"
import * as google from "./passport/google" export * as google from "./passport/google"
import * as oidc from "./passport/oidc" export * as oidc from "./passport/oidc"
import { authError, ssoCallbackUrl } from "./passport/utils"
import authenticated from "./authenticated"
import auditLog from "./auditLog"
import tenancy from "./tenancy"
import internalApi from "./internalApi"
import * as datasourceGoogle from "./passport/datasource/google" import * as datasourceGoogle from "./passport/datasource/google"
import csrf from "./csrf" export const datasource = {
import adminOnly from "./adminOnly" google: datasourceGoogle,
import builderOrAdmin from "./builderOrAdmin"
import builderOnly from "./builderOnly"
import * as joiValidator from "./joi-validator"
const pkg = {
google,
oidc,
jwt,
local,
authenticated,
auditLog,
tenancy,
authError,
internalApi,
ssoCallbackUrl,
datasource: {
google: datasourceGoogle,
},
csrf,
adminOnly,
builderOnly,
builderOrAdmin,
joiValidator,
} }
export { authError, ssoCallbackUrl } from "./passport/utils"
export = pkg export { default as authenticated } from "./authenticated"
export { default as auditLog } from "./auditLog"
export { default as tenancy } from "./tenancy"
export { default as internalApi } from "./internalApi"
export { default as csrf } from "./csrf"
export { default as adminOnly } from "./adminOnly"
export { default as builderOrAdmin } from "./builderOrAdmin"
export { default as builderOnly } from "./builderOnly"
export { default as logging } from "./logging"
export * as joiValidator from "./joi-validator"

View File

@ -5,7 +5,7 @@ import { BBContext } from "@budibase/types"
/** /**
* API Key only endpoint. * API Key only endpoint.
*/ */
export = async (ctx: BBContext, next: any) => { export default async (ctx: BBContext, next: any) => {
const apiKey = ctx.request.headers[Header.API_KEY] const apiKey = ctx.request.headers[Header.API_KEY]
if (apiKey !== env.INTERNAL_API_KEY) { if (apiKey !== env.INTERNAL_API_KEY) {
ctx.throw(403, "Unauthorized") ctx.throw(403, "Unauthorized")

View File

@ -0,0 +1,88 @@
const correlator = require("correlation-id")
import { Header } from "../constants"
import { v4 as uuid } from "uuid"
import * as context from "../context"
const debug = console.warn
const trace = console.trace
const log = console.log
const info = console.info
const warn = console.warn
const error = console.error
const getTenantId = () => {
let tenantId
try {
tenantId = context.getTenantId()
} catch (e: any) {
// do nothing
}
return tenantId
}
const getAppId = () => {
let appId
try {
appId = context.getAppId()
} catch (e) {
// do nothing
}
return appId
}
const getIdentityId = () => {
let identityId
try {
const identity = context.getIdentity()
identityId = identity?._id
} catch (e) {
// do nothing
}
return identityId
}
const print = (fn: any, data: any[]) => {
let message = ""
const correlationId = correlator.getId()
if (correlationId) {
message = message + `[correlationId=${correlator.getId()}]`
}
const tenantId = getTenantId()
if (tenantId) {
message = message + ` [tenantId=${tenantId}]`
}
const appId = getAppId()
if (appId) {
message = message + ` [appId=${appId}]`
}
const identityId = getIdentityId()
if (identityId) {
message = message + ` [identityId=${identityId}]`
}
fn(message, data)
}
const logging = (ctx: any, next: any) => {
// use the provided correlation id header if present
let correlationId = ctx.headers[Header.CORRELATION_ID]
if (!correlationId) {
correlationId = uuid()
}
return correlator.withId(correlationId, () => {
console.debug = data => print(debug, data)
console.trace = data => print(trace, data)
console.log = data => print(log, data)
console.info = data => print(info, data)
console.warn = data => print(warn, data)
console.error = data => print(error, data)
return next()
})
}
export default logging

View File

@ -2,7 +2,6 @@ import fetch from "node-fetch"
import { authenticateThirdParty, SaveUserFunction } from "./third-party-common" import { authenticateThirdParty, SaveUserFunction } from "./third-party-common"
import { ssoCallbackUrl } from "./utils" import { ssoCallbackUrl } from "./utils"
import { import {
Config,
ConfigType, ConfigType,
OIDCInnerCfg, OIDCInnerCfg,
Database, Database,

View File

@ -8,7 +8,7 @@ import {
TenantResolutionStrategy, TenantResolutionStrategy,
} from "@budibase/types" } from "@budibase/types"
export = function ( export default function (
allowQueryStringPatterns: EndpointMatcher[], allowQueryStringPatterns: EndpointMatcher[],
noTenancyPatterns: EndpointMatcher[], noTenancyPatterns: EndpointMatcher[],
opts: { noTenancyRequired?: boolean } = { noTenancyRequired: false } opts: { noTenancyRequired?: boolean } = { noTenancyRequired: false }

View File

@ -88,7 +88,7 @@ export const runMigration = async (
await doWithDB(dbName, async (db: any) => { await doWithDB(dbName, async (db: any) => {
try { try {
const doc = await exports.getMigrationsDoc(db) const doc = await getMigrationsDoc(db)
// the migration has already been run // the migration has already been run
if (doc[migrationName]) { if (doc[migrationName]) {

View File

@ -0,0 +1,40 @@
import env from "../../environment"
import * as objectStore from "../objectStore"
import * as cloudfront from "../cloudfront"
/**
* In production the client library is stored in the object store, however in development
* we use the symlinked version produced by lerna, located in node modules. We link to this
* via a specific endpoint (under /api/assets/client).
* @param {string} appId In production we need the appId to look up the correct bucket, as the
* version of the client lib may differ between apps.
* @param {string} version The version to retrieve.
* @return {string} The URL to be inserted into appPackage response or server rendered
* app index file.
*/
export const clientLibraryUrl = (appId: string, version: string) => {
if (env.isProd()) {
let file = `${objectStore.sanitizeKey(appId)}/budibase-client.js`
if (env.CLOUDFRONT_CDN) {
// append app version to bust the cache
if (version) {
file += `?v=${version}`
}
// don't need to use presigned for client with cloudfront
// file is public
return cloudfront.getUrl(file)
} else {
return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
}
} else {
return `/api/assets/client`
}
}
export const getAppFileUrl = (s3Key: string) => {
if (env.CLOUDFRONT_CDN) {
return cloudfront.getPresignedUrl(s3Key)
} else {
return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, s3Key)
}
}

View File

@ -0,0 +1,29 @@
import env from "../../environment"
import * as tenancy from "../../tenancy"
import * as objectStore from "../objectStore"
import * as cloudfront from "../cloudfront"
// URLs
export const getGlobalFileUrl = (type: string, name: string, etag?: string) => {
let file = getGlobalFileS3Key(type, name)
if (env.CLOUDFRONT_CDN) {
if (etag) {
file = `${file}?etag=${etag}`
}
return cloudfront.getPresignedUrl(file)
} else {
return objectStore.getPresignedUrl(env.GLOBAL_BUCKET_NAME, file)
}
}
// KEYS
export const getGlobalFileS3Key = (type: string, name: string) => {
let file = `${type}/${name}`
if (env.MULTI_TENANCY) {
const tenantId = tenancy.getTenantId()
file = `${tenantId}/${file}`
}
return file
}

View File

@ -0,0 +1,3 @@
export * from "./app"
export * from "./global"
export * from "./plugins"

View File

@ -0,0 +1,71 @@
import env from "../../environment"
import * as objectStore from "../objectStore"
import * as tenancy from "../../tenancy"
import * as cloudfront from "../cloudfront"
import { Plugin } from "@budibase/types"
// URLS
export const enrichPluginURLs = (plugins: Plugin[]) => {
if (!plugins || !plugins.length) {
return []
}
return plugins.map(plugin => {
const jsUrl = getPluginJSUrl(plugin)
const iconUrl = getPluginIconUrl(plugin)
return { ...plugin, jsUrl, iconUrl }
})
}
const getPluginJSUrl = (plugin: Plugin) => {
const s3Key = getPluginJSKey(plugin)
return getPluginUrl(s3Key)
}
const getPluginIconUrl = (plugin: Plugin): string | undefined => {
const s3Key = getPluginIconKey(plugin)
if (!s3Key) {
return
}
return getPluginUrl(s3Key)
}
const getPluginUrl = (s3Key: string) => {
if (env.CLOUDFRONT_CDN) {
return cloudfront.getPresignedUrl(s3Key)
} else {
return objectStore.getPresignedUrl(env.PLUGIN_BUCKET_NAME, s3Key)
}
}
// S3 KEYS
export const getPluginJSKey = (plugin: Plugin) => {
return getPluginS3Key(plugin, "plugin.min.js")
}
export const getPluginIconKey = (plugin: Plugin) => {
// stored iconUrl is deprecated - hardcode to icon.svg in this case
const iconFileName = plugin.iconUrl ? "icon.svg" : plugin.iconFileName
if (!iconFileName) {
return
}
return getPluginS3Key(plugin, iconFileName)
}
const getPluginS3Key = (plugin: Plugin, fileName: string) => {
const s3Key = getPluginS3Dir(plugin.name)
return `${s3Key}/${fileName}`
}
export const getPluginS3Dir = (pluginName: string) => {
let s3Key = `${pluginName}`
if (env.MULTI_TENANCY) {
const tenantId = tenancy.getTenantId()
s3Key = `${tenantId}/${s3Key}`
}
if (env.CLOUDFRONT_CDN) {
s3Key = `plugins/${s3Key}`
}
return s3Key
}

View File

@ -0,0 +1,171 @@
import * as app from "../app"
import { getAppFileUrl } from "../app"
import { testEnv } from "../../../../tests"
describe("app", () => {
beforeEach(() => {
testEnv.nodeJest()
})
describe("clientLibraryUrl", () => {
function getClientUrl() {
return app.clientLibraryUrl("app_123/budibase-client.js", "2.0.0")
}
describe("single tenant", () => {
beforeAll(() => {
testEnv.singleTenant()
})
it("gets url in dev", () => {
testEnv.nodeDev()
const url = getClientUrl()
expect(url).toBe("/api/assets/client")
})
it("gets url with embedded minio", () => {
testEnv.withMinio()
const url = getClientUrl()
expect(url).toBe(
"/files/signed/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
)
})
it("gets url with custom S3", () => {
testEnv.withS3()
const url = getClientUrl()
expect(url).toBe(
"http://s3.example.com/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
)
})
it("gets url with cloudfront + s3", () => {
testEnv.withCloudfront()
const url = getClientUrl()
expect(url).toBe(
"http://cf.example.com/app_123/budibase-client.js/budibase-client.js?v=2.0.0"
)
})
})
describe("multi tenant", () => {
beforeAll(() => {
testEnv.multiTenant()
})
it("gets url in dev", async () => {
testEnv.nodeDev()
await testEnv.withTenant(tenantId => {
const url = getClientUrl()
expect(url).toBe("/api/assets/client")
})
})
it("gets url with embedded minio", async () => {
await testEnv.withTenant(tenantId => {
testEnv.withMinio()
const url = getClientUrl()
expect(url).toBe(
"/files/signed/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
)
})
})
it("gets url with custom S3", async () => {
await testEnv.withTenant(tenantId => {
testEnv.withS3()
const url = getClientUrl()
expect(url).toBe(
"http://s3.example.com/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
)
})
})
it("gets url with cloudfront + s3", async () => {
await testEnv.withTenant(tenantId => {
testEnv.withCloudfront()
const url = getClientUrl()
expect(url).toBe(
"http://cf.example.com/app_123/budibase-client.js/budibase-client.js?v=2.0.0"
)
})
})
})
})
describe("getAppFileUrl", () => {
function getAppFileUrl() {
return app.getAppFileUrl("app_123/attachments/image.jpeg")
}
describe("single tenant", () => {
beforeAll(() => {
testEnv.multiTenant()
})
it("gets url with embedded minio", () => {
testEnv.withMinio()
const url = getAppFileUrl()
expect(url).toBe(
"/files/signed/prod-budi-app-assets/app_123/attachments/image.jpeg"
)
})
it("gets url with custom S3", () => {
testEnv.withS3()
const url = getAppFileUrl()
expect(url).toBe(
"http://s3.example.com/prod-budi-app-assets/app_123/attachments/image.jpeg"
)
})
it("gets url with cloudfront + s3", () => {
testEnv.withCloudfront()
const url = getAppFileUrl()
// omit rest of signed params
expect(
url.includes("http://cf.example.com/app_123/attachments/image.jpeg?")
).toBe(true)
})
})
describe("multi tenant", () => {
beforeAll(() => {
testEnv.multiTenant()
})
it("gets url with embedded minio", async () => {
testEnv.withMinio()
await testEnv.withTenant(tenantId => {
const url = getAppFileUrl()
expect(url).toBe(
"/files/signed/prod-budi-app-assets/app_123/attachments/image.jpeg"
)
})
})
it("gets url with custom S3", async () => {
testEnv.withS3()
await testEnv.withTenant(tenantId => {
const url = getAppFileUrl()
expect(url).toBe(
"http://s3.example.com/prod-budi-app-assets/app_123/attachments/image.jpeg"
)
})
})
it("gets url with cloudfront + s3", async () => {
testEnv.withCloudfront()
await testEnv.withTenant(tenantId => {
const url = getAppFileUrl()
// omit rest of signed params
expect(
url.includes(
"http://cf.example.com/app_123/attachments/image.jpeg?"
)
).toBe(true)
})
})
})
})
})

View File

@ -0,0 +1,74 @@
import * as global from "../global"
import { testEnv } from "../../../../tests"
describe("global", () => {
describe("getGlobalFileUrl", () => {
function getGlobalFileUrl() {
return global.getGlobalFileUrl("settings", "logoUrl", "etag")
}
describe("single tenant", () => {
beforeAll(() => {
testEnv.singleTenant()
})
it("gets url with embedded minio", () => {
testEnv.withMinio()
const url = getGlobalFileUrl()
expect(url).toBe("/files/signed/global/settings/logoUrl")
})
it("gets url with custom S3", () => {
testEnv.withS3()
const url = getGlobalFileUrl()
expect(url).toBe("http://s3.example.com/global/settings/logoUrl")
})
it("gets url with cloudfront + s3", () => {
testEnv.withCloudfront()
const url = getGlobalFileUrl()
// omit rest of signed params
expect(
url.includes("http://cf.example.com/settings/logoUrl?etag=etag&")
).toBe(true)
})
})
describe("multi tenant", () => {
beforeAll(() => {
testEnv.multiTenant()
})
it("gets url with embedded minio", async () => {
testEnv.withMinio()
await testEnv.withTenant(tenantId => {
const url = getGlobalFileUrl()
expect(url).toBe(`/files/signed/global/${tenantId}/settings/logoUrl`)
})
})
it("gets url with custom S3", async () => {
testEnv.withS3()
await testEnv.withTenant(tenantId => {
const url = getGlobalFileUrl()
expect(url).toBe(
`http://s3.example.com/global/${tenantId}/settings/logoUrl`
)
})
})
it("gets url with cloudfront + s3", async () => {
testEnv.withCloudfront()
await testEnv.withTenant(tenantId => {
const url = getGlobalFileUrl()
// omit rest of signed params
expect(
url.includes(
`http://cf.example.com/${tenantId}/settings/logoUrl?etag=etag&`
)
).toBe(true)
})
})
})
})
})

View File

@ -0,0 +1,110 @@
import * as plugins from "../plugins"
import { structures, testEnv } from "../../../../tests"
describe("plugins", () => {
describe("enrichPluginURLs", () => {
const plugin = structures.plugins.plugin()
function getEnrichedPluginUrls() {
const enriched = plugins.enrichPluginURLs([plugin])[0]
return {
jsUrl: enriched.jsUrl!,
iconUrl: enriched.iconUrl!,
}
}
describe("single tenant", () => {
beforeAll(() => {
testEnv.singleTenant()
})
it("gets url with embedded minio", () => {
testEnv.withMinio()
const urls = getEnrichedPluginUrls()
expect(urls.jsUrl).toBe(
`/files/signed/plugins/${plugin.name}/plugin.min.js`
)
expect(urls.iconUrl).toBe(
`/files/signed/plugins/${plugin.name}/icon.svg`
)
})
it("gets url with custom S3", () => {
testEnv.withS3()
const urls = getEnrichedPluginUrls()
expect(urls.jsUrl).toBe(
`http://s3.example.com/plugins/${plugin.name}/plugin.min.js`
)
expect(urls.iconUrl).toBe(
`http://s3.example.com/plugins/${plugin.name}/icon.svg`
)
})
it("gets url with cloudfront + s3", () => {
testEnv.withCloudfront()
const urls = getEnrichedPluginUrls()
// omit rest of signed params
expect(
urls.jsUrl.includes(
`http://cf.example.com/plugins/${plugin.name}/plugin.min.js?`
)
).toBe(true)
expect(
urls.iconUrl.includes(
`http://cf.example.com/plugins/${plugin.name}/icon.svg?`
)
).toBe(true)
})
})
describe("multi tenant", () => {
beforeAll(() => {
testEnv.multiTenant()
})
it("gets url with embedded minio", async () => {
testEnv.withMinio()
await testEnv.withTenant(tenantId => {
const urls = getEnrichedPluginUrls()
expect(urls.jsUrl).toBe(
`/files/signed/plugins/${tenantId}/${plugin.name}/plugin.min.js`
)
expect(urls.iconUrl).toBe(
`/files/signed/plugins/${tenantId}/${plugin.name}/icon.svg`
)
})
})
it("gets url with custom S3", async () => {
testEnv.withS3()
await testEnv.withTenant(tenantId => {
const urls = getEnrichedPluginUrls()
expect(urls.jsUrl).toBe(
`http://s3.example.com/plugins/${tenantId}/${plugin.name}/plugin.min.js`
)
expect(urls.iconUrl).toBe(
`http://s3.example.com/plugins/${tenantId}/${plugin.name}/icon.svg`
)
})
})
it("gets url with cloudfront + s3", async () => {
testEnv.withCloudfront()
await testEnv.withTenant(tenantId => {
const urls = getEnrichedPluginUrls()
// omit rest of signed params
expect(
urls.jsUrl.includes(
`http://cf.example.com/plugins/${tenantId}/${plugin.name}/plugin.min.js?`
)
).toBe(true)
expect(
urls.iconUrl.includes(
`http://cf.example.com/plugins/${tenantId}/${plugin.name}/icon.svg?`
)
).toBe(true)
})
})
})
})
})

View File

@ -0,0 +1,41 @@
import env from "../environment"
const cfsign = require("aws-cloudfront-sign")
let PRIVATE_KEY: string | undefined
function getPrivateKey() {
if (!env.CLOUDFRONT_PRIVATE_KEY_64) {
throw new Error("CLOUDFRONT_PRIVATE_KEY_64 is not set")
}
if (PRIVATE_KEY) {
return PRIVATE_KEY
}
PRIVATE_KEY = Buffer.from(env.CLOUDFRONT_PRIVATE_KEY_64, "base64").toString(
"utf-8"
)
return PRIVATE_KEY
}
const getCloudfrontSignParams = () => {
return {
keypairId: env.CLOUDFRONT_PUBLIC_KEY_ID,
privateKeyString: getPrivateKey(),
expireTime: new Date().getTime() + 1000 * 60 * 60, // 1 hour
}
}
export const getPresignedUrl = (s3Key: string) => {
const url = getUrl(s3Key)
return cfsign.getSignedUrl(url, getCloudfrontSignParams())
}
export const getUrl = (s3Key: string) => {
let prefix = "/"
if (s3Key.startsWith("/")) {
prefix = ""
}
return `${env.CLOUDFRONT_CDN}${prefix}${s3Key}`
}

View File

@ -1,2 +1,3 @@
export * from "./objectStore" export * from "./objectStore"
export * from "./utils" export * from "./utils"
export * from "./buckets"

View File

@ -8,7 +8,7 @@ import { promisify } from "util"
import { join } from "path" import { join } from "path"
import fs from "fs" import fs from "fs"
import env from "../environment" import env from "../environment"
import { budibaseTempDir, ObjectStoreBuckets } from "./utils" import { budibaseTempDir } from "./utils"
import { v4 } from "uuid" import { v4 } from "uuid"
import { APP_PREFIX, APP_DEV_PREFIX } from "../db" import { APP_PREFIX, APP_DEV_PREFIX } from "../db"
@ -26,7 +26,7 @@ type UploadParams = {
bucket: string bucket: string
filename: string filename: string
path: string path: string
type?: string type?: string | null
// can be undefined, we will remove it // can be undefined, we will remove it
metadata?: { metadata?: {
[key: string]: string | undefined [key: string]: string | undefined
@ -41,6 +41,7 @@ const CONTENT_TYPE_MAP: any = {
json: "application/json", json: "application/json",
gz: "application/gzip", gz: "application/gzip",
} }
const STRING_CONTENT_TYPES = [ const STRING_CONTENT_TYPES = [
CONTENT_TYPE_MAP.html, CONTENT_TYPE_MAP.html,
CONTENT_TYPE_MAP.css, CONTENT_TYPE_MAP.css,
@ -58,35 +59,17 @@ export function sanitizeBucket(input: string) {
return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX) return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX)
} }
function publicPolicy(bucketName: string) {
return {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: {
AWS: ["*"],
},
Action: "s3:GetObject",
Resource: [`arn:aws:s3:::${bucketName}/*`],
},
],
}
}
const PUBLIC_BUCKETS = [
ObjectStoreBuckets.APPS,
ObjectStoreBuckets.GLOBAL,
ObjectStoreBuckets.PLUGINS,
]
/** /**
* Gets a connection to the object store using the S3 SDK. * Gets a connection to the object store using the S3 SDK.
* @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from. * @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from.
* @param {object} opts configuration for the object store.
* @return {Object} an S3 object store object, check S3 Nodejs SDK for usage. * @return {Object} an S3 object store object, check S3 Nodejs SDK for usage.
* @constructor * @constructor
*/ */
export const ObjectStore = (bucket: string) => { export const ObjectStore = (
bucket: string,
opts: { presigning: boolean } = { presigning: false }
) => {
const config: any = { const config: any = {
s3ForcePathStyle: true, s3ForcePathStyle: true,
signatureVersion: "v4", signatureVersion: "v4",
@ -100,9 +83,20 @@ export const ObjectStore = (bucket: string) => {
Bucket: sanitizeBucket(bucket), Bucket: sanitizeBucket(bucket),
} }
} }
// custom S3 is in use i.e. minio
if (env.MINIO_URL) { if (env.MINIO_URL) {
config.endpoint = env.MINIO_URL if (opts.presigning && env.MINIO_ENABLED) {
// IMPORTANT: Signed urls will inspect the host header of the request.
// Normally a signed url will need to be generated with a specified host in mind.
// To support dynamic hosts, e.g. some unknown self-hosted installation url,
// use a predefined host. The host 'minio-service' is also forwarded to minio requests via nginx
config.endpoint = "minio-service"
} else {
config.endpoint = env.MINIO_URL
}
} }
return new AWS.S3(config) return new AWS.S3(config)
} }
@ -135,16 +129,6 @@ export const makeSureBucketExists = async (client: any, bucketName: string) => {
await promises[bucketName] await promises[bucketName]
delete promises[bucketName] delete promises[bucketName]
} }
// public buckets are quite hidden in the system, make sure
// no bucket is set accidentally
if (PUBLIC_BUCKETS.includes(bucketName)) {
await client
.putBucketPolicy({
Bucket: bucketName,
Policy: JSON.stringify(publicPolicy(bucketName)),
})
.promise()
}
} else { } else {
throw new Error("Unable to write to object store bucket.") throw new Error("Unable to write to object store bucket.")
} }
@ -274,6 +258,36 @@ export const listAllObjects = async (bucketName: string, path: string) => {
return objects return objects
} }
/**
* Generate a presigned url with a default TTL of 1 hour
*/
export const getPresignedUrl = (
bucketName: string,
key: string,
durationSeconds: number = 3600
) => {
const objectStore = ObjectStore(bucketName, { presigning: true })
const params = {
Bucket: sanitizeBucket(bucketName),
Key: sanitizeKey(key),
Expires: durationSeconds,
}
const url = objectStore.getSignedUrl("getObject", params)
if (!env.MINIO_ENABLED) {
// return the full URL to the client
return url
} else {
// return the path only to the client
// use the presigned url route to ensure the static
// hostname will be used in the request
const signedUrl = new URL(url)
const path = signedUrl.pathname
const query = signedUrl.search
return `/files/signed${path}${query}`
}
}
/** /**
* Same as retrieval function but puts to a temporary file. * Same as retrieval function but puts to a temporary file.
*/ */
@ -315,9 +329,9 @@ export const deleteFile = async (bucketName: string, filepath: string) => {
await makeSureBucketExists(objectStore, bucketName) await makeSureBucketExists(objectStore, bucketName)
const params = { const params = {
Bucket: bucketName, Bucket: bucketName,
Key: filepath, Key: sanitizeKey(filepath),
} }
return objectStore.deleteObject(params) return objectStore.deleteObject(params).promise()
} }
export const deleteFiles = async (bucketName: string, filepaths: string[]) => { export const deleteFiles = async (bucketName: string, filepaths: string[]) => {
@ -326,7 +340,7 @@ export const deleteFiles = async (bucketName: string, filepaths: string[]) => {
const params = { const params = {
Bucket: bucketName, Bucket: bucketName,
Delete: { Delete: {
Objects: filepaths.map((path: any) => ({ Key: path })), Objects: filepaths.map((path: any) => ({ Key: sanitizeKey(path) })),
}, },
} }
return objectStore.deleteObjects(params).promise() return objectStore.deleteObjects(params).promise()

View File

@ -14,7 +14,6 @@ export const ObjectStoreBuckets = {
APPS: env.APPS_BUCKET_NAME, APPS: env.APPS_BUCKET_NAME,
TEMPLATES: env.TEMPLATES_BUCKET_NAME, TEMPLATES: env.TEMPLATES_BUCKET_NAME,
GLOBAL: env.GLOBAL_BUCKET_NAME, GLOBAL: env.GLOBAL_BUCKET_NAME,
GLOBAL_CLOUD: env.GLOBAL_CLOUD_BUCKET_NAME,
PLUGINS: env.PLUGIN_BUCKET_NAME, PLUGINS: env.PLUGIN_BUCKET_NAME,
} }

View File

@ -1,13 +0,0 @@
import env from "./environment"
export function pinoSettings() {
return {
prettyPrint: {
levelFirst: true,
},
level: env.LOG_LEVEL || "error",
autoLogging: {
ignore: (req: { url: string }) => req.url.includes("/health"),
},
}
}

View File

@ -137,4 +137,4 @@ class InMemoryQueue {
} }
} }
export = InMemoryQueue export default InMemoryQueue

View File

@ -276,4 +276,4 @@ class RedisWrapper {
} }
} }
export = RedisWrapper export default RedisWrapper

Some files were not shown because too many files have changed in this diff Show More