merge with master

This commit is contained in:
Martin McKeaveney 2022-06-08 13:26:14 +01:00
commit c470c2f676
39 changed files with 228 additions and 92 deletions

View File

@ -48,7 +48,7 @@ http {
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com"; set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
set $csp_object "object-src 'none'"; set $csp_object "object-src 'none'";
set $csp_base_uri "base-uri 'self'"; set $csp_base_uri "base-uri 'self'";
set $csp_connect "connect-src 'self' https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com"; set $csp_connect "connect-src 'self' https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com";
set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com"; set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com";
set $csp_frame "frame-src 'self' https:"; set $csp_frame "frame-src 'self' https:";
set $csp_img "img-src http: https: data: blob:"; set $csp_img "img-src http: https: data: blob:";

View File

@ -1,5 +1,5 @@
{ {
"version": "1.0.192-alpha.1", "version": "1.0.198",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",

View File

@ -1,4 +1,5 @@
const google = require("../google") const google = require("../google")
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
const { Cookies, Configs } = require("../../../constants") const { Cookies, Configs } = require("../../../constants")
const { clearCookie, getCookie } = require("../../../utils") const { clearCookie, getCookie } = require("../../../utils")
const { getScopedConfig, getPlatformUrl } = require("../../../db/utils") const { getScopedConfig, getPlatformUrl } = require("../../../db/utils")
@ -46,19 +47,20 @@ async function postAuth(passport, ctx, next) {
const platformUrl = await getPlatformUrl({ tenantAware: false }) const platformUrl = await getPlatformUrl({ tenantAware: false })
let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback` let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback`
const strategy = await google.strategyFactory(
config,
callbackUrl,
(accessToken, refreshToken, profile, done) => {
clearCookie(ctx, Cookies.DatasourceAuth)
done(null, { refreshToken })
}
)
const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth) const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth)
return passport.authenticate( return passport.authenticate(
strategy, new GoogleStrategy(
{
clientID: config.clientID,
clientSecret: config.clientSecret,
callbackURL: callbackUrl,
},
(accessToken, refreshToken, profile, done) => {
clearCookie(ctx, Cookies.DatasourceAuth)
done(null, { accessToken, refreshToken })
}
),
{ successRedirect: "/", failureRedirect: "/error" }, { successRedirect: "/", failureRedirect: "/error" },
async (err, tokens) => { async (err, tokens) => {
// update the DB for the datasource with all the user info // update the DB for the datasource with all the user info

View File

@ -11,8 +11,8 @@ const buildVerifyFn = saveUserFn => {
profile: profile, profile: profile,
email: profile._json.email, email: profile._json.email,
oauth2: { oauth2: {
accessToken: accessToken, accessToken,
refreshToken: refreshToken, refreshToken,
}, },
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1", "@adobe/spectrum-css-workflow-icons": "^1.2.1",
"@budibase/string-templates": "^1.0.192-alpha.1", "@budibase/string-templates": "^1.0.198",
"@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2", "@spectrum-css/avatar": "^3.0.2",

View File

@ -15,6 +15,7 @@
export let placeholder = null export let placeholder = null
export let appendTo = undefined export let appendTo = undefined
export let timeOnly = false export let timeOnly = false
export let ignoreTimezones = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const flatpickrId = `${uuid()}-wrapper` const flatpickrId = `${uuid()}-wrapper`
@ -50,19 +51,35 @@
const handleChange = event => { const handleChange = event => {
const [dates] = event.detail const [dates] = event.detail
const noTimezone = enableTime && !timeOnly && ignoreTimezones
let newValue = dates[0] let newValue = dates[0]
if (newValue) { if (newValue) {
newValue = newValue.toISOString() newValue = newValue.toISOString()
} }
// if time only set date component to 2000-01-01
// If time only set date component to 2000-01-01
if (timeOnly) { if (timeOnly) {
newValue = `2000-01-01T${newValue.split("T")[1]}` newValue = `2000-01-01T${newValue.split("T")[1]}`
} }
// date only, offset for timezone so always right date
// For date-only fields, construct a manual timestamp string without a time
// or time zone
else if (!enableTime) { else if (!enableTime) {
const offset = dates[0].getTimezoneOffset() * 60000 const year = dates[0].getFullYear()
newValue = new Date(dates[0].getTime() - offset).toISOString() const month = `${dates[0].getMonth() + 1}`.padStart(2, "0")
const day = `${dates[0].getDate()}`.padStart(2, "0")
newValue = `${year}-${month}-${day}T00:00:00.000`
} }
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
// time picked, without timezone
else if (noTimezone) {
const offset = dates[0].getTimezoneOffset() * 60000
newValue = new Date(dates[0].getTime() - offset)
.toISOString()
.slice(0, -1)
}
dispatch("change", newValue) dispatch("change", newValue)
} }
@ -112,10 +129,12 @@
// Treat as numerical timestamp // Treat as numerical timestamp
date = new Date(parseInt(val)) date = new Date(parseInt(val))
} }
time = date.getTime() time = date.getTime()
if (isNaN(time)) { if (isNaN(time)) {
return null return null
} }
// By rounding to the nearest second we avoid locking up in an endless // By rounding to the nearest second we avoid locking up in an endless
// loop in the builder, caused by potentially enriching {{ now }} to every // loop in the builder, caused by potentially enriching {{ now }} to every
// millisecond. // millisecond.

View File

@ -12,6 +12,7 @@
export let timeOnly = false export let timeOnly = false
export let placeholder = null export let placeholder = null
export let appendTo = undefined export let appendTo = undefined
export let ignoreTimezones = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -30,6 +31,7 @@
{enableTime} {enableTime}
{timeOnly} {timeOnly}
{appendTo} {appendTo}
{ignoreTimezones}
on:change={onChange} on:change={onChange}
/> />
</Field> </Field>

View File

@ -28,7 +28,7 @@
export let rowCount = 0 export let rowCount = 0
export let quiet = false export let quiet = false
export let loading = false export let loading = false
export let allowSelectRows = true export let allowSelectRows
export let allowEditRows = true export let allowEditRows = true
export let allowEditColumns = true export let allowEditColumns = true
export let selectedRows = [] export let selectedRows = []
@ -344,11 +344,7 @@
{/if} {/if}
{#if sortedRows?.length} {#if sortedRows?.length}
{#each sortedRows as row, idx} {#each sortedRows as row, idx}
<div <div class="spectrum-Table-row">
class="spectrum-Table-row"
on:click={() => dispatch("click", row)}
on:click={() => toggleSelectRow(row)}
>
{#if showEditColumn} {#if showEditColumn}
<div <div
class="spectrum-Table-cell spectrum-Table-cell--divider spectrum-Table-cell--edit" class="spectrum-Table-cell spectrum-Table-cell--divider spectrum-Table-cell--edit"
@ -373,6 +369,12 @@
class="spectrum-Table-cell" class="spectrum-Table-cell"
class:spectrum-Table-cell--divider={!!schema[field].divider} class:spectrum-Table-cell--divider={!!schema[field].divider}
style={cellStyles[field]} style={cellStyles[field]}
on:click={() => {
if (!schema[field]?.preventSelectRow) {
dispatch("click", row)
toggleSelectRow(row)
}
}}
> >
<CellRenderer <CellRenderer
{customRenderers} {customRenderers}

View File

@ -39,7 +39,6 @@
position: relative; position: relative;
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-top: 1px;
margin-left: 5px; margin-left: 5px;
margin-right: 5px; margin-right: 5px;
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -69,10 +69,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.192-alpha.1", "@budibase/bbui": "^1.0.198",
"@budibase/client": "^1.0.192-alpha.1", "@budibase/client": "^1.0.198",
"@budibase/frontend-core": "^1.0.192-alpha.1", "@budibase/frontend-core": "^1.0.198",
"@budibase/string-templates": "^1.0.192-alpha.1", "@budibase/string-templates": "^1.0.198",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -170,28 +170,29 @@ export function makeDatasourceFormComponents(datasource) {
optionsType: "select", optionsType: "select",
optionsSource: "schema", optionsSource: "schema",
}) })
} } else if (fieldType === "longform") {
if (fieldType === "longform") {
component.customProps({ component.customProps({
format: "auto", format: "auto",
}) })
} } else if (fieldType === "array") {
if (fieldType === "array") {
component.customProps({ component.customProps({
placeholder: "Choose an option", placeholder: "Choose an option",
optionsSource: "schema", optionsSource: "schema",
}) })
} } else if (fieldType === "link") {
if (fieldType === "link") {
let placeholder = let placeholder =
fieldSchema.relationshipType === "one-to-many" fieldSchema.relationshipType === "one-to-many"
? "Choose an option" ? "Choose an option"
: "Choose some options" : "Choose some options"
component.customProps({ placeholder }) component.customProps({ placeholder })
} } else if (fieldType === "boolean") {
if (fieldType === "boolean") {
component.customProps({ text: field, label: "" }) component.customProps({ text: field, label: "" })
} else if (fieldType === "datetime") {
component.customProps({
enableTime: !fieldSchema?.dateOnly,
timeOnly: fieldSchema?.timeOnly,
ignoreTimezones: fieldSchema.ignoreTimezones,
})
} }
components.push(component) components.push(component)
} }

View File

@ -53,6 +53,7 @@
{label} {label}
timeOnly={isTimeStamp} timeOnly={isTimeStamp}
enableTime={!meta?.dateOnly} enableTime={!meta?.dateOnly}
ignoreTimezones={meta?.ignoreTimezones}
bind:value bind:value
/> />
{:else if type === "attachment"} {:else if type === "attachment"}

View File

@ -14,7 +14,7 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import { createEventDispatcher, onMount } from "svelte" import { createEventDispatcher, onMount } from "svelte"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { tables } from "stores/backend" import { tables, datasources } from "stores/backend"
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
import { import {
FIELDS, FIELDS,
@ -63,6 +63,7 @@
let primaryDisplay = let primaryDisplay =
$tables.selected.primaryDisplay == null || $tables.selected.primaryDisplay == null ||
$tables.selected.primaryDisplay === field.name $tables.selected.primaryDisplay === field.name
let isCreating = originalName == null
let table = $tables.selected let table = $tables.selected
let indexes = [...($tables.selected.indexes || [])] let indexes = [...($tables.selected.indexes || [])]
@ -81,6 +82,9 @@
(field.type === LINK_TYPE && !field.tableId) || (field.type === LINK_TYPE && !field.tableId) ||
Object.keys(errors).length !== 0 Object.keys(errors).length !== 0
$: errors = checkErrors(field) $: errors = checkErrors(field)
$: datasource = $datasources.list.find(
source => source._id === table?.sourceId
)
// used to select what different options can be displayed for column type // used to select what different options can be displayed for column type
$: canBeSearched = $: canBeSearched =
@ -430,6 +434,18 @@
bind:value={field.constraints.datetime.earliest} bind:value={field.constraints.datetime.earliest}
/> />
<DatePicker label="Latest" bind:value={field.constraints.datetime.latest} /> <DatePicker label="Latest" bind:value={field.constraints.datetime.latest} />
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
<div>
<Label
tooltip={isCreating
? null
: "We recommend not changing how timezones are handled for existing columns, as existing data will not be updated"}
>
Time zones
</Label>
<Toggle bind:value={field.ignoreTimezones} text="Ignore time zones" />
</div>
{/if}
{:else if field.type === "number"} {:else if field.type === "number"}
<Input <Input
type="number" type="number"

View File

@ -30,6 +30,6 @@ export default {
REST: Rest, REST: Rest,
ORACLE: Oracle, ORACLE: Oracle,
GOOGLE_SHEETS: GoogleSheets, GOOGLE_SHEETS: GoogleSheets,
FIREBASE: Firebase, FIRESTORE: Firebase,
REDIS: Redis, REDIS: Redis,
} }

View File

@ -8,7 +8,9 @@
export let linkedRows = [] export let linkedRows = []
let rows = [] let rows = []
let linkedIds = (linkedRows || [])?.map(row => row?._id || row) let linkedIds = (Array.isArray(linkedRows) ? linkedRows : [])?.map(
row => row?._id || row
)
$: linkedRows = linkedIds $: linkedRows = linkedIds
$: label = capitalise(schema.name) $: label = capitalise(schema.name)

View File

@ -182,7 +182,7 @@ export const IntegrationTypes = {
ORACLE: "ORACLE", ORACLE: "ORACLE",
INTERNAL: "INTERNAL", INTERNAL: "INTERNAL",
GOOGLE_SHEETS: "GOOGLE_SHEETS", GOOGLE_SHEETS: "GOOGLE_SHEETS",
FIREBASE: "FIREBASE", FIRESTORE: "FIRESTORE",
REDIS: "REDIS", REDIS: "REDIS",
} }
@ -201,7 +201,7 @@ export const IntegrationNames = {
[IntegrationTypes.ORACLE]: "Oracle", [IntegrationTypes.ORACLE]: "Oracle",
[IntegrationTypes.INTERNAL]: "Internal", [IntegrationTypes.INTERNAL]: "Internal",
[IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets", [IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets",
[IntegrationTypes.FIREBASE]: "Firebase", [IntegrationTypes.FIRESTORE]: "Firestore",
[IntegrationTypes.REDIS]: "Redis", [IntegrationTypes.REDIS]: "Redis",
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View File

@ -2620,16 +2620,22 @@
}, },
{ {
"type": "boolean", "type": "boolean",
"label": "Show Time", "label": "Show time",
"key": "enableTime", "key": "enableTime",
"defaultValue": true "defaultValue": true
}, },
{ {
"type": "boolean", "type": "boolean",
"label": "Time Only", "label": "Time only",
"key": "timeOnly", "key": "timeOnly",
"defaultValue": false "defaultValue": false
}, },
{
"type": "boolean",
"label": "Ignore time zones",
"key": "ignoreTimezones",
"defaultValue": false
},
{ {
"type": "text", "type": "text",
"label": "Default value", "label": "Default value",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.192-alpha.1", "@budibase/bbui": "^1.0.198",
"@budibase/frontend-core": "^1.0.192-alpha.1", "@budibase/frontend-core": "^1.0.198",
"@budibase/string-templates": "^1.0.192-alpha.1", "@budibase/string-templates": "^1.0.198",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View File

@ -8,6 +8,7 @@
export let disabled = false export let disabled = false
export let enableTime = false export let enableTime = false
export let timeOnly = false export let timeOnly = false
export let ignoreTimezones = false
export let validation export let validation
export let defaultValue export let defaultValue
export let onChange export let onChange
@ -43,6 +44,7 @@
appendTo={document.getElementById("flatpickr-root")} appendTo={document.getElementById("flatpickr-root")}
{enableTime} {enableTime}
{timeOnly} {timeOnly}
{ignoreTimezones}
{placeholder} {placeholder}
/> />
{/if} {/if}

View File

@ -86,6 +86,7 @@
sortable: false, sortable: false,
divider: true, divider: true,
width: "auto", width: "auto",
preventSelectRow: true,
} }
} }

View File

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.192-alpha.1", "@budibase/bbui": "^1.0.198",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -70,10 +70,10 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "^10.0.3", "@apidevtools/swagger-parser": "^10.0.3",
"@budibase/backend-core": "^1.0.192-alpha.1", "@budibase/backend-core": "^1.0.198",
"@budibase/client": "^1.0.192-alpha.1", "@budibase/client": "^1.0.198",
"@budibase/pro": "1.0.192-alpha.1", "@budibase/pro": "1.0.198",
"@budibase/string-templates": "^1.0.192-alpha.1", "@budibase/string-templates": "^1.0.198",
"@bull-board/api": "^3.7.0", "@bull-board/api": "^3.7.0",
"@bull-board/koa": "^3.7.0", "@bull-board/koa": "^3.7.0",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View File

@ -29,7 +29,10 @@ import { breakExternalTableId, isSQL } from "../../../integrations/utils"
import { processObjectSync } from "@budibase/string-templates" import { processObjectSync } from "@budibase/string-templates"
// @ts-ignore // @ts-ignore
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { processFormulas } from "../../../utilities/rowProcessor/utils" import {
processFormulas,
processDates,
} from "../../../utilities/rowProcessor/utils"
// @ts-ignore // @ts-ignore
import { getAppDB } from "@budibase/backend-core/context" import { getAppDB } from "@budibase/backend-core/context"
@ -434,7 +437,13 @@ module External {
relationships relationships
) )
} }
return processFormulas(table, Object.values(finalRows)).map((row: Row) =>
// Process some additional data types
let finalRowArray = Object.values(finalRows)
finalRowArray = processDates(table, finalRowArray)
finalRowArray = processFormulas(table, finalRowArray)
return finalRowArray.map((row: Row) =>
this.squashRelationshipColumns(table, row, relationships) this.squashRelationshipColumns(table, row, relationships)
) )
} }

View File

@ -25,6 +25,7 @@ export interface FieldSchema {
formula?: string formula?: string
formulaType?: string formulaType?: string
main?: boolean main?: boolean
ignoreTimezones?: boolean
meta?: { meta?: {
toTable: string toTable: string
toKey: string toKey: string

View File

@ -48,7 +48,7 @@ export enum SourceNames {
REST = "REST", REST = "REST",
ORACLE = "ORACLE", ORACLE = "ORACLE",
GOOGLE_SHEETS = "GOOGLE_SHEETS", GOOGLE_SHEETS = "GOOGLE_SHEETS",
FIREBASE = "FIREBASE", FIRESTORE = "FIRESTORE",
REDIS = "REDIS", REDIS = "REDIS",
} }

View File

@ -61,7 +61,9 @@ function generateSchema(
schema.boolean(key) schema.boolean(key)
break break
case FieldTypes.DATETIME: case FieldTypes.DATETIME:
schema.datetime(key) schema.datetime(key, {
useTz: !column.ignoreTimezones,
})
break break
case FieldTypes.ARRAY: case FieldTypes.ARRAY:
schema.json(key) schema.json(key)

View File

@ -164,11 +164,15 @@ module GoogleSheetsModule {
} }
) )
const json = await response.json()
if (response.status !== 200) { if (response.status !== 200) {
throw new Error("Error authenticating with google sheets.") throw new Error(
`Error authenticating with google sheets. ${json.error_description}`
)
} }
return response.json() return json
} }
async connect() { async connect() {

View File

@ -27,7 +27,7 @@ const DEFINITIONS = {
[SourceNames.MYSQL]: mysql.schema, [SourceNames.MYSQL]: mysql.schema,
[SourceNames.ARANGODB]: arangodb.schema, [SourceNames.ARANGODB]: arangodb.schema,
[SourceNames.REST]: rest.schema, [SourceNames.REST]: rest.schema,
[SourceNames.FIREBASE]: firebase.schema, [SourceNames.FIRESTORE]: firebase.schema,
[SourceNames.REDIS]: redis.schema, [SourceNames.REDIS]: redis.schema,
} }
@ -43,10 +43,9 @@ const INTEGRATIONS = {
[SourceNames.MYSQL]: mysql.integration, [SourceNames.MYSQL]: mysql.integration,
[SourceNames.ARANGODB]: arangodb.integration, [SourceNames.ARANGODB]: arangodb.integration,
[SourceNames.REST]: rest.integration, [SourceNames.REST]: rest.integration,
[SourceNames.FIREBASE]: firebase.integration, [SourceNames.FIRESTORE]: firebase.integration,
[SourceNames.GOOGLE_SHEETS]: googlesheets.integration, [SourceNames.GOOGLE_SHEETS]: googlesheets.integration,
[SourceNames.REDIS]: redis.integration, [SourceNames.REDIS]: redis.integration,
[SourceNames.FIREBASE]: firebase.integration,
} }
// optionally add oracle integration if the oracle binary can be installed // optionally add oracle integration if the oracle binary can be installed

View File

@ -15,7 +15,6 @@ import {
} from "./utils" } from "./utils"
import { DatasourcePlus } from "./base/datasourcePlus" import { DatasourcePlus } from "./base/datasourcePlus"
import dayjs from "dayjs" import dayjs from "dayjs"
import { FieldTypes } from "../constants"
const { NUMBER_REGEX } = require("../utilities") const { NUMBER_REGEX } = require("../utilities")
module MySQLModule { module MySQLModule {
@ -30,6 +29,7 @@ module MySQLModule {
database: string database: string
ssl?: { [key: string]: any } ssl?: { [key: string]: any }
rejectUnauthorized: boolean rejectUnauthorized: boolean
typeCast: Function
} }
const SCHEMA: Integration = { const SCHEMA: Integration = {
@ -89,6 +89,8 @@ module MySQLModule {
}, },
} }
const TimezoneAwareDateTypes = ["timestamp"]
function bindingTypeCoerce(bindings: any[]) { function bindingTypeCoerce(bindings: any[]) {
for (let i = 0; i < bindings.length; i++) { for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i] const binding = bindings[i]
@ -131,7 +133,19 @@ module MySQLModule {
} }
// @ts-ignore // @ts-ignore
delete config.rejectUnauthorized delete config.rejectUnauthorized
this.config = config this.config = {
...config,
typeCast: function (field: any, next: any) {
if (
field.type == "DATETIME" ||
field.type === "DATE" ||
field.type === "TIMESTAMP"
) {
return field.string()
}
return next()
},
}
} }
getBindingIdentifier(): string { getBindingIdentifier(): string {

View File

@ -16,10 +16,19 @@ import {
import { DatasourcePlus } from "./base/datasourcePlus" import { DatasourcePlus } from "./base/datasourcePlus"
module PostgresModule { module PostgresModule {
const { Client } = require("pg") const { Client, types } = require("pg")
const Sql = require("./base/sql") const Sql = require("./base/sql")
const { escapeDangerousCharacters } = require("../utilities") const { escapeDangerousCharacters } = require("../utilities")
// Return "date" and "timestamp" types as plain strings.
// This lets us reference the original stored timezone.
// types is undefined when running in a test env for some reason.
if (types) {
types.setTypeParser(1114, (val: any) => val) // timestamp
types.setTypeParser(1082, (val: any) => val) // date
types.setTypeParser(1184, (val: any) => val) // timestampz
}
const JSON_REGEX = /'{.*}'::json/s const JSON_REGEX = /'{.*}'::json/s
interface PostgresConfig { interface PostgresConfig {

View File

@ -65,3 +65,28 @@ exports.processFormulas = (
} }
return single ? rows[0] : rows return single ? rows[0] : rows
} }
/**
* Processes any date columns and ensures that those without the ignoreTimezones
* flag set are parsed as UTC rather than local time.
*/
exports.processDates = (table, rows) => {
let datesWithTZ = []
for (let [column, schema] of Object.entries(table.schema)) {
if (schema.type !== FieldTypes.DATETIME) {
continue
}
if (!schema.ignoreTimezones) {
datesWithTZ.push(column)
}
}
for (let row of rows) {
for (let col of datesWithTZ) {
if (row[col] && typeof row[col] === "string" && !row[col].endsWith("Z")) {
row[col] = new Date(row[col]).toISOString()
}
}
}
return rows
}

View File

@ -1014,10 +1014,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.0.192-alpha.1": "@budibase/backend-core@1.0.197":
version "1.0.192-alpha.1" version "1.0.197"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.192-alpha.1.tgz#62ecb0afc4cc2cb48ac93dd15a812f6f3fe1a40c" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.197.tgz#3458d70c6d44376b7930672d6af8c6e89ddf4069"
integrity sha512-2WzYIg12ZEOcCwdywqyC9ZrJlXqg8w4xI6T+ba7Fei1RACo0y4QfUoNN+Ik1NP+cpyagVf2HbpYr/yMZqdJYUA== integrity sha512-Cgzr1bJWKRg3+jqte7rnKPziWiH5Q+r/piRvHuD7EVmh2+xJLfWUz9iml72aFfcgRIOX8SyhejG7KTwxILx/vg==
dependencies: dependencies:
"@techpass/passport-openidconnect" "^0.3.0" "@techpass/passport-openidconnect" "^0.3.0"
aws-sdk "^2.901.0" aws-sdk "^2.901.0"
@ -1092,12 +1092,21 @@
svelte-flatpickr "^3.2.3" svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0" svelte-portal "^1.0.0"
<<<<<<< HEAD
"@budibase/pro@1.0.192-alpha.1": "@budibase/pro@1.0.192-alpha.1":
version "1.0.192-alpha.1" version "1.0.192-alpha.1"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.192-alpha.1.tgz#f3dece7ee153ac81080be9f96c7459e5dd8510ff" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.192-alpha.1.tgz#f3dece7ee153ac81080be9f96c7459e5dd8510ff"
integrity sha512-BRzoRTIcIW/o2bckSmOrOYCz5HPJyeq4PurfEZFHh16dXPXSBSTIuIiC7Q1jic2CBq5DJOfT/SYTgj/OTVga3g== integrity sha512-BRzoRTIcIW/o2bckSmOrOYCz5HPJyeq4PurfEZFHh16dXPXSBSTIuIiC7Q1jic2CBq5DJOfT/SYTgj/OTVga3g==
dependencies: dependencies:
"@budibase/backend-core" "1.0.192-alpha.1" "@budibase/backend-core" "1.0.192-alpha.1"
=======
"@budibase/pro@1.0.197":
version "1.0.197"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.197.tgz#a171b46bb8ee6251881ae9262136270533b3958d"
integrity sha512-SCVKjNgpzefmrXnLmkpQJLvYViykyzA6B9TwL7qrb6fBeXwAiSZ3hXGjNgZkVpy/v43hccPWt9BJFzFp457wgQ==
dependencies:
"@budibase/backend-core" "1.0.197"
>>>>>>> bd0ea6fd3ce54fc0f7f8774656d127a91771ea2d
node-fetch "^2.6.1" node-fetch "^2.6.1"
"@budibase/standard-components@^0.9.139": "@budibase/standard-components@^0.9.139":

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.0.192-alpha.1", "version": "1.0.198",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -32,9 +32,9 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "^1.0.192-alpha.1", "@budibase/backend-core": "^1.0.198",
"@budibase/pro": "1.0.192-alpha.1", "@budibase/pro": "1.0.198",
"@budibase/string-templates": "^1.0.192-alpha.1", "@budibase/string-templates": "^1.0.198",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",

View File

@ -6,6 +6,7 @@ const Joi = require("joi")
const cloudRestricted = require("../../../middleware/cloudRestricted") const cloudRestricted = require("../../../middleware/cloudRestricted")
const { buildUserSaveValidation } = require("../../utilities/validation") const { buildUserSaveValidation } = require("../../utilities/validation")
const selfController = require("../../controllers/global/self") const selfController = require("../../controllers/global/self")
const builderOrAdmin = require("../../../middleware/builderOrAdmin")
const router = Router() const router = Router()
@ -44,7 +45,7 @@ router
buildUserSaveValidation(), buildUserSaveValidation(),
controller.save controller.save
) )
.get("/api/global/users", adminOnly, controller.fetch) .get("/api/global/users", builderOrAdmin, controller.fetch)
.delete("/api/global/users/:id", adminOnly, controller.destroy) .delete("/api/global/users/:id", adminOnly, controller.destroy)
.get("/api/global/roles/:appId") .get("/api/global/roles/:appId")
.post( .post(

View File

@ -0,0 +1,10 @@
module.exports = async (ctx, next) => {
if (
!ctx.internal &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global) &&
(!ctx.user || !ctx.user.admin || !ctx.user.admin.global)
) {
ctx.throw(403, "Builder user only endpoint.")
}
return next()
}

View File

@ -293,10 +293,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.0.192-alpha.1": "@budibase/backend-core@1.0.197":
version "1.0.192-alpha.1" version "1.0.197"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.192-alpha.1.tgz#62ecb0afc4cc2cb48ac93dd15a812f6f3fe1a40c" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.197.tgz#3458d70c6d44376b7930672d6af8c6e89ddf4069"
integrity sha512-2WzYIg12ZEOcCwdywqyC9ZrJlXqg8w4xI6T+ba7Fei1RACo0y4QfUoNN+Ik1NP+cpyagVf2HbpYr/yMZqdJYUA== integrity sha512-Cgzr1bJWKRg3+jqte7rnKPziWiH5Q+r/piRvHuD7EVmh2+xJLfWUz9iml72aFfcgRIOX8SyhejG7KTwxILx/vg==
dependencies: dependencies:
"@techpass/passport-openidconnect" "^0.3.0" "@techpass/passport-openidconnect" "^0.3.0"
aws-sdk "^2.901.0" aws-sdk "^2.901.0"
@ -322,12 +322,12 @@
uuid "^8.3.2" uuid "^8.3.2"
zlib "^1.0.5" zlib "^1.0.5"
"@budibase/pro@1.0.192-alpha.1": "@budibase/pro@1.0.197":
version "1.0.192-alpha.1" version "1.0.197"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.192-alpha.1.tgz#f3dece7ee153ac81080be9f96c7459e5dd8510ff" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.197.tgz#a171b46bb8ee6251881ae9262136270533b3958d"
integrity sha512-BRzoRTIcIW/o2bckSmOrOYCz5HPJyeq4PurfEZFHh16dXPXSBSTIuIiC7Q1jic2CBq5DJOfT/SYTgj/OTVga3g== integrity sha512-SCVKjNgpzefmrXnLmkpQJLvYViykyzA6B9TwL7qrb6fBeXwAiSZ3hXGjNgZkVpy/v43hccPWt9BJFzFp457wgQ==
dependencies: dependencies:
"@budibase/backend-core" "1.0.192-alpha.1" "@budibase/backend-core" "1.0.197"
node-fetch "^2.6.1" node-fetch "^2.6.1"
"@cspotcode/source-map-consumer@0.8.0": "@cspotcode/source-map-consumer@0.8.0":