merge with master
This commit is contained in:
commit
df927e3feb
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: Request a new budibase feature or enhancement
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the feature request**
|
||||||
|
A clear and concise description of what the feature request.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
|
@ -57,7 +57,7 @@
|
||||||
|
|
||||||
- **Open source and extensible.** Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
- **Open source and extensible.** Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.
|
||||||
|
|
||||||
- **Load data or start from scratch.** Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, mySQL, Airtable, S3, DyanmoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
- **Load data or start from scratch.** Budibase pulls in data from multiple sources, including MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new data sources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||||
|
|
||||||
- **Design and build apps with powerful pre-made components.** Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
- **Design and build apps with powerful pre-made components.** Budibase comes out of the box with beautifully designed, powerful components which you can use like building blocks to build your UI. We also expose a lot of your favourite CSS styling options so you can go that extra creative mile. [Request new component](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).
|
||||||
|
|
||||||
|
|
|
@ -26,18 +26,10 @@ static_resources:
|
||||||
cluster: couchdb-service
|
cluster: couchdb-service
|
||||||
prefix_rewrite: "/"
|
prefix_rewrite: "/"
|
||||||
|
|
||||||
- match: { prefix: "/api/system/" }
|
|
||||||
route:
|
|
||||||
cluster: worker-dev
|
|
||||||
|
|
||||||
- match: { prefix: "/api/admin/" }
|
- match: { prefix: "/api/admin/" }
|
||||||
route:
|
route:
|
||||||
cluster: worker-dev
|
cluster: worker-dev
|
||||||
|
|
||||||
- match: { prefix: "/api/global/" }
|
|
||||||
route:
|
|
||||||
cluster: worker-dev
|
|
||||||
|
|
||||||
- match: { prefix: "/api/" }
|
- match: { prefix: "/api/" }
|
||||||
route:
|
route:
|
||||||
cluster: server-dev
|
cluster: server-dev
|
||||||
|
|
|
@ -37,19 +37,11 @@ static_resources:
|
||||||
route:
|
route:
|
||||||
cluster: app-service
|
cluster: app-service
|
||||||
|
|
||||||
# special cases for worker admin (deprecated), global and system API
|
# special case for worker admin API
|
||||||
- match: { prefix: "/api/global/" }
|
|
||||||
route:
|
|
||||||
cluster: worker-service
|
|
||||||
|
|
||||||
- match: { prefix: "/api/admin/" }
|
- match: { prefix: "/api/admin/" }
|
||||||
route:
|
route:
|
||||||
cluster: worker-service
|
cluster: worker-service
|
||||||
|
|
||||||
- match: { prefix: "/api/system/" }
|
|
||||||
route:
|
|
||||||
cluster: worker-service
|
|
||||||
|
|
||||||
- match: { path: "/" }
|
- match: { path: "/" }
|
||||||
route:
|
route:
|
||||||
cluster: app-service
|
cluster: app-service
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.9.87-alpha.9",
|
"version": "0.9.95",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -43,8 +43,6 @@
|
||||||
"test:e2e": "lerna run cy:test",
|
"test:e2e": "lerna run cy:test",
|
||||||
"test:e2e:ci": "lerna run cy:ci",
|
"test:e2e:ci": "lerna run cy:ci",
|
||||||
"build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -",
|
"build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -",
|
||||||
"build:docker:develop": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",
|
"build:docker:develop": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -"
|
||||||
"multi:enable": "lerna run multi:enable",
|
|
||||||
"multi:disable": "lerna run multi:disable"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
module.exports = {
|
module.exports = require("./src/db/utils")
|
||||||
...require("./src/db/utils"),
|
|
||||||
...require("./src/db/constants"),
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/auth",
|
"name": "@budibase/auth",
|
||||||
"version": "0.9.87-alpha.9",
|
"version": "0.9.95",
|
||||||
"description": "Authentication middlewares for budibase builder and apps",
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
@ -13,7 +13,6 @@
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
"aws-sdk": "^2.901.0",
|
"aws-sdk": "^2.901.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cls-hooked": "^4.2.2",
|
|
||||||
"ioredis": "^4.27.1",
|
"ioredis": "^4.27.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"koa-passport": "^4.1.4",
|
"koa-passport": "^4.1.4",
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
|
const { getDB } = require("../db")
|
||||||
|
const { StaticDatabases } = require("../db/utils")
|
||||||
const redis = require("../redis/authRedis")
|
const redis = require("../redis/authRedis")
|
||||||
const { getTenantId, lookupTenantId, getGlobalDB } = require("../tenancy")
|
|
||||||
|
|
||||||
const EXPIRY_SECONDS = 3600
|
const EXPIRY_SECONDS = 3600
|
||||||
|
|
||||||
exports.getUser = async (userId, tenantId = null) => {
|
exports.getUser = async userId => {
|
||||||
if (!tenantId) {
|
|
||||||
try {
|
|
||||||
tenantId = getTenantId()
|
|
||||||
} catch (err) {
|
|
||||||
tenantId = await lookupTenantId(userId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const client = await redis.getUserClient()
|
const client = await redis.getUserClient()
|
||||||
// try cache
|
// try cache
|
||||||
let user = await client.get(userId)
|
let user = await client.get(userId)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await getGlobalDB(tenantId).get(userId)
|
user = await getDB(StaticDatabases.GLOBAL.name).get(userId)
|
||||||
client.store(userId, user, EXPIRY_SECONDS)
|
client.store(userId, user, EXPIRY_SECONDS)
|
||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
|
|
|
@ -14,14 +14,13 @@ exports.Headers = {
|
||||||
API_VER: "x-budibase-api-version",
|
API_VER: "x-budibase-api-version",
|
||||||
APP_ID: "x-budibase-app-id",
|
APP_ID: "x-budibase-app-id",
|
||||||
TYPE: "x-budibase-type",
|
TYPE: "x-budibase-type",
|
||||||
TENANT_ID: "x-budibase-tenant-id",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.GlobalRoles = {
|
exports.GlobalRoles = {
|
||||||
OWNER: "owner",
|
OWNER: "owner",
|
||||||
ADMIN: "admin",
|
ADMIN: "admin",
|
||||||
BUILDER: "builder",
|
BUILDER: "builder",
|
||||||
WORKSPACE_MANAGER: "workspace_manager",
|
GROUP_MANAGER: "group_manager",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.Configs = {
|
exports.Configs = {
|
||||||
|
@ -32,5 +31,3 @@ exports.Configs = {
|
||||||
OIDC: "oidc",
|
OIDC: "oidc",
|
||||||
OIDC_LOGOS: "logos_oidc",
|
OIDC_LOGOS: "logos_oidc",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.DEFAULT_TENANT_ID = "default"
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
exports.SEPARATOR = "_"
|
|
||||||
|
|
||||||
exports.StaticDatabases = {
|
|
||||||
GLOBAL: {
|
|
||||||
name: "global-db",
|
|
||||||
docs: {
|
|
||||||
apiKeys: "apikeys",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// contains information about tenancy and so on
|
|
||||||
PLATFORM_INFO: {
|
|
||||||
name: "global-info",
|
|
||||||
docs: {
|
|
||||||
tenants: "tenants",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,36 +1,36 @@
|
||||||
const { newid } = require("../hashing")
|
const { newid } = require("../hashing")
|
||||||
const Replication = require("./Replication")
|
const Replication = require("./Replication")
|
||||||
const { DEFAULT_TENANT_ID } = require("../constants")
|
|
||||||
const env = require("../environment")
|
|
||||||
const { StaticDatabases, SEPARATOR } = require("./constants")
|
|
||||||
const { getTenantId } = require("../tenancy")
|
|
||||||
|
|
||||||
const UNICODE_MAX = "\ufff0"
|
const UNICODE_MAX = "\ufff0"
|
||||||
|
const SEPARATOR = "_"
|
||||||
|
|
||||||
exports.ViewNames = {
|
exports.ViewNames = {
|
||||||
USER_BY_EMAIL: "by_email",
|
USER_BY_EMAIL: "by_email",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.StaticDatabases = StaticDatabases
|
exports.StaticDatabases = {
|
||||||
|
GLOBAL: {
|
||||||
const PRE_APP = "app"
|
name: "global-db",
|
||||||
const PRE_DEV = "dev"
|
},
|
||||||
|
DEPLOYMENTS: {
|
||||||
|
name: "deployments",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const DocumentTypes = {
|
const DocumentTypes = {
|
||||||
USER: "us",
|
USER: "us",
|
||||||
WORKSPACE: "workspace",
|
GROUP: "group",
|
||||||
CONFIG: "config",
|
CONFIG: "config",
|
||||||
TEMPLATE: "template",
|
TEMPLATE: "template",
|
||||||
APP: PRE_APP,
|
APP: "app",
|
||||||
DEV: PRE_DEV,
|
APP_DEV: "app_dev",
|
||||||
APP_DEV: `${PRE_APP}${SEPARATOR}${PRE_DEV}`,
|
APP_METADATA: "app_metadata",
|
||||||
APP_METADATA: `${PRE_APP}${SEPARATOR}metadata`,
|
|
||||||
ROLE: "role",
|
ROLE: "role",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.DocumentTypes = DocumentTypes
|
exports.DocumentTypes = DocumentTypes
|
||||||
exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
exports.APP_DEV = exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR
|
exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR
|
||||||
exports.SEPARATOR = SEPARATOR
|
exports.SEPARATOR = SEPARATOR
|
||||||
|
|
||||||
function isDevApp(app) {
|
function isDevApp(app) {
|
||||||
|
@ -61,21 +61,21 @@ function getDocParams(docType, docId = null, otherProps = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new workspace ID.
|
* Generates a new group ID.
|
||||||
* @returns {string} The new workspace ID which the workspace doc can be stored under.
|
* @returns {string} The new group ID which the group doc can be stored under.
|
||||||
*/
|
*/
|
||||||
exports.generateWorkspaceID = () => {
|
exports.generateGroupID = () => {
|
||||||
return `${DocumentTypes.WORKSPACE}${SEPARATOR}${newid()}`
|
return `${DocumentTypes.GROUP}${SEPARATOR}${newid()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets parameters for retrieving workspaces.
|
* Gets parameters for retrieving groups.
|
||||||
*/
|
*/
|
||||||
exports.getWorkspaceParams = (id = "", otherProps = {}) => {
|
exports.getGroupParams = (id = "", otherProps = {}) => {
|
||||||
return {
|
return {
|
||||||
...otherProps,
|
...otherProps,
|
||||||
startkey: `${DocumentTypes.WORKSPACE}${SEPARATOR}${id}`,
|
startkey: `${DocumentTypes.GROUP}${SEPARATOR}${id}`,
|
||||||
endkey: `${DocumentTypes.WORKSPACE}${SEPARATOR}${id}${UNICODE_MAX}`,
|
endkey: `${DocumentTypes.GROUP}${SEPARATOR}${id}${UNICODE_MAX}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,14 +103,14 @@ exports.getGlobalUserParams = (globalId, otherProps = {}) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a template ID.
|
* Generates a template ID.
|
||||||
* @param ownerId The owner/user of the template, this could be global or a workspace level.
|
* @param ownerId The owner/user of the template, this could be global or a group level.
|
||||||
*/
|
*/
|
||||||
exports.generateTemplateID = ownerId => {
|
exports.generateTemplateID = ownerId => {
|
||||||
return `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}`
|
return `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets parameters for retrieving templates. Owner ID must be specified, either global or a workspace level.
|
* Gets parameters for retrieving templates. Owner ID must be specified, either global or a group level.
|
||||||
*/
|
*/
|
||||||
exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => {
|
exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => {
|
||||||
if (!templateId) {
|
if (!templateId) {
|
||||||
|
@ -163,26 +163,11 @@ exports.getDeployedAppID = appId => {
|
||||||
* different users/companies apps as there is no security around it - all apps are returned.
|
* different users/companies apps as there is no security around it - all apps are returned.
|
||||||
* @return {Promise<object[]>} returns the app information document stored in each app database.
|
* @return {Promise<object[]>} returns the app information document stored in each app database.
|
||||||
*/
|
*/
|
||||||
exports.getAllApps = async (CouchDB, { dev, all } = {}) => {
|
exports.getAllApps = async ({ CouchDB, dev, all } = {}) => {
|
||||||
let tenantId = getTenantId()
|
|
||||||
if (!env.MULTI_TENANCY && !tenantId) {
|
|
||||||
tenantId = DEFAULT_TENANT_ID
|
|
||||||
}
|
|
||||||
let allDbs = await CouchDB.allDbs()
|
let allDbs = await CouchDB.allDbs()
|
||||||
const appDbNames = allDbs.filter(dbName => {
|
const appDbNames = allDbs.filter(dbName =>
|
||||||
const split = dbName.split(SEPARATOR)
|
dbName.startsWith(exports.APP_PREFIX)
|
||||||
// it is an app, check the tenantId
|
)
|
||||||
if (split[0] === DocumentTypes.APP) {
|
|
||||||
const noTenantId = split.length === 2 || split[1] === DocumentTypes.DEV
|
|
||||||
// tenantId is always right before the UUID
|
|
||||||
const possibleTenantId = split[split.length - 2]
|
|
||||||
return (
|
|
||||||
(tenantId === DEFAULT_TENANT_ID && noTenantId) ||
|
|
||||||
possibleTenantId === tenantId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
const appPromises = appDbNames.map(db =>
|
const appPromises = appDbNames.map(db =>
|
||||||
// skip setup otherwise databases could be re-created
|
// skip setup otherwise databases could be re-created
|
||||||
new CouchDB(db, { skip_setup: true }).get(DocumentTypes.APP_METADATA)
|
new CouchDB(db, { skip_setup: true }).get(DocumentTypes.APP_METADATA)
|
||||||
|
@ -229,8 +214,8 @@ exports.dbExists = async (CouchDB, dbName) => {
|
||||||
* Generates a new configuration ID.
|
* Generates a new configuration ID.
|
||||||
* @returns {string} The new configuration ID which the config doc can be stored under.
|
* @returns {string} The new configuration ID which the config doc can be stored under.
|
||||||
*/
|
*/
|
||||||
const generateConfigID = ({ type, workspace, user }) => {
|
const generateConfigID = ({ type, group, user }) => {
|
||||||
const scope = [type, workspace, user].filter(Boolean).join(SEPARATOR)
|
const scope = [type, group, user].filter(Boolean).join(SEPARATOR)
|
||||||
|
|
||||||
return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`
|
return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`
|
||||||
}
|
}
|
||||||
|
@ -238,8 +223,8 @@ const generateConfigID = ({ type, workspace, user }) => {
|
||||||
/**
|
/**
|
||||||
* Gets parameters for retrieving configurations.
|
* Gets parameters for retrieving configurations.
|
||||||
*/
|
*/
|
||||||
const getConfigParams = ({ type, workspace, user }, otherProps = {}) => {
|
const getConfigParams = ({ type, group, user }, otherProps = {}) => {
|
||||||
const scope = [type, workspace, user].filter(Boolean).join(SEPARATOR)
|
const scope = [type, group, user].filter(Boolean).join(SEPARATOR)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...otherProps,
|
...otherProps,
|
||||||
|
@ -249,15 +234,15 @@ const getConfigParams = ({ type, workspace, user }, otherProps = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the most granular configuration document from the DB based on the type, workspace and userID passed.
|
* Returns the most granular configuration document from the DB based on the type, group and userID passed.
|
||||||
* @param {Object} db - db instance to query
|
* @param {Object} db - db instance to query
|
||||||
* @param {Object} scopes - the type, workspace and userID scopes of the configuration.
|
* @param {Object} scopes - the type, group and userID scopes of the configuration.
|
||||||
* @returns The most granular configuration document based on the scope.
|
* @returns The most granular configuration document based on the scope.
|
||||||
*/
|
*/
|
||||||
const getScopedFullConfig = async function (db, { type, user, workspace }) {
|
const getScopedFullConfig = async function (db, { type, user, group }) {
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
getConfigParams(
|
getConfigParams(
|
||||||
{ type, user, workspace },
|
{ type, user, group },
|
||||||
{
|
{
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
}
|
}
|
||||||
|
@ -267,14 +252,14 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) {
|
||||||
function determineScore(row) {
|
function determineScore(row) {
|
||||||
const config = row.doc
|
const config = row.doc
|
||||||
|
|
||||||
// Config is specific to a user and a workspace
|
// Config is specific to a user and a group
|
||||||
if (config._id.includes(generateConfigID({ type, user, workspace }))) {
|
if (config._id.includes(generateConfigID({ type, user, group }))) {
|
||||||
return 4
|
return 4
|
||||||
} else if (config._id.includes(generateConfigID({ type, user }))) {
|
} else if (config._id.includes(generateConfigID({ type, user }))) {
|
||||||
// Config is specific to a user only
|
// Config is specific to a user only
|
||||||
return 3
|
return 3
|
||||||
} else if (config._id.includes(generateConfigID({ type, workspace }))) {
|
} else if (config._id.includes(generateConfigID({ type, group }))) {
|
||||||
// Config is specific to a workspace only
|
// Config is specific to a group only
|
||||||
return 2
|
return 2
|
||||||
} else if (config._id.includes(generateConfigID({ type }))) {
|
} else if (config._id.includes(generateConfigID({ type }))) {
|
||||||
// Config is specific to a type only
|
// Config is specific to a type only
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const { DocumentTypes, ViewNames } = require("./utils")
|
const { DocumentTypes, ViewNames, StaticDatabases } = require("./utils")
|
||||||
|
const { getDB } = require("./index")
|
||||||
|
|
||||||
function DesignDoc() {
|
function DesignDoc() {
|
||||||
return {
|
return {
|
||||||
|
@ -9,7 +10,8 @@ function DesignDoc() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createUserEmailView = async db => {
|
exports.createUserEmailView = async () => {
|
||||||
|
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||||
let designDoc
|
let designDoc
|
||||||
try {
|
try {
|
||||||
designDoc = await db.get("_design/database")
|
designDoc = await db.get("_design/database")
|
||||||
|
|
|
@ -16,7 +16,6 @@ module.exports = {
|
||||||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
||||||
MINIO_URL: process.env.MINIO_URL,
|
MINIO_URL: process.env.MINIO_URL,
|
||||||
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
||||||
MULTI_TENANCY: process.env.MULTI_TENANCY,
|
|
||||||
isTest,
|
isTest,
|
||||||
_set(key, value) {
|
_set(key, value) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
|
|
|
@ -2,7 +2,6 @@ const passport = require("koa-passport")
|
||||||
const LocalStrategy = require("passport-local").Strategy
|
const LocalStrategy = require("passport-local").Strategy
|
||||||
const JwtStrategy = require("passport-jwt").Strategy
|
const JwtStrategy = require("passport-jwt").Strategy
|
||||||
const { StaticDatabases } = require("./db/utils")
|
const { StaticDatabases } = require("./db/utils")
|
||||||
const { getGlobalDB } = require("./tenancy")
|
|
||||||
const {
|
const {
|
||||||
jwt,
|
jwt,
|
||||||
local,
|
local,
|
||||||
|
@ -10,9 +9,8 @@ const {
|
||||||
google,
|
google,
|
||||||
oidc,
|
oidc,
|
||||||
auditLog,
|
auditLog,
|
||||||
tenancy,
|
|
||||||
} = require("./middleware")
|
} = require("./middleware")
|
||||||
const { setDB } = require("./db")
|
const { setDB, getDB } = require("./db")
|
||||||
const userCache = require("./cache/user")
|
const userCache = require("./cache/user")
|
||||||
|
|
||||||
// Strategies
|
// Strategies
|
||||||
|
@ -22,7 +20,7 @@ passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
|
||||||
passport.serializeUser((user, done) => done(null, user))
|
passport.serializeUser((user, done) => done(null, user))
|
||||||
|
|
||||||
passport.deserializeUser(async (user, done) => {
|
passport.deserializeUser(async (user, done) => {
|
||||||
const db = getGlobalDB()
|
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await db.get(user._id)
|
const user = await db.get(user._id)
|
||||||
|
@ -56,7 +54,6 @@ module.exports = {
|
||||||
google,
|
google,
|
||||||
oidc,
|
oidc,
|
||||||
jwt: require("jsonwebtoken"),
|
jwt: require("jsonwebtoken"),
|
||||||
buildTenancyMiddleware: tenancy,
|
|
||||||
auditLog,
|
auditLog,
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
|
|
|
@ -2,34 +2,46 @@ const { Cookies, Headers } = require("../constants")
|
||||||
const { getCookie, clearCookie } = require("../utils")
|
const { getCookie, clearCookie } = require("../utils")
|
||||||
const { getUser } = require("../cache/user")
|
const { getUser } = require("../cache/user")
|
||||||
const { getSession, updateSessionTTL } = require("../security/sessions")
|
const { getSession, updateSessionTTL } = require("../security/sessions")
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
|
|
||||||
function finalise(
|
const PARAM_REGEX = /\/:(.*?)\//g
|
||||||
ctx,
|
|
||||||
{ authenticated, user, internal, version, publicEndpoint } = {}
|
function buildNoAuthRegex(patterns) {
|
||||||
) {
|
return patterns.map(pattern => {
|
||||||
ctx.publicEndpoint = publicEndpoint || false
|
const isObj = typeof pattern === "object" && pattern.route
|
||||||
|
const method = isObj ? pattern.method : "GET"
|
||||||
|
let route = isObj ? pattern.route : pattern
|
||||||
|
|
||||||
|
const matches = route.match(PARAM_REGEX)
|
||||||
|
if (matches) {
|
||||||
|
for (let match of matches) {
|
||||||
|
route = route.replace(match, "/.*/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { regex: new RegExp(route), method }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalise(ctx, { authenticated, user, internal, version } = {}) {
|
||||||
ctx.isAuthenticated = authenticated || false
|
ctx.isAuthenticated = authenticated || false
|
||||||
ctx.user = user
|
ctx.user = user
|
||||||
ctx.internal = internal || false
|
ctx.internal = internal || false
|
||||||
ctx.version = version
|
ctx.version = version
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
module.exports = (noAuthPatterns = [], opts) => {
|
||||||
* This middleware is tenancy aware, so that it does not depend on other middlewares being used.
|
const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : []
|
||||||
* The tenancy modules should not be used here and it should be assumed that the tenancy context
|
|
||||||
* has not yet been populated.
|
|
||||||
*/
|
|
||||||
module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => {
|
|
||||||
const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
|
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
let publicEndpoint = false
|
|
||||||
const version = ctx.request.headers[Headers.API_VER]
|
const version = ctx.request.headers[Headers.API_VER]
|
||||||
// the path is not authenticated
|
// the path is not authenticated
|
||||||
const found = matches(ctx, noAuthOptions)
|
const found = noAuthOptions.find(({ regex, method }) => {
|
||||||
if (found) {
|
return (
|
||||||
publicEndpoint = true
|
regex.test(ctx.request.url) &&
|
||||||
|
ctx.request.method.toLowerCase() === method.toLowerCase()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if (found != null) {
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// check the actual user is authenticated first
|
// check the actual user is authenticated first
|
||||||
|
@ -46,7 +58,7 @@ module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => {
|
||||||
error = "No session found"
|
error = "No session found"
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
user = await getUser(userId, session.tenantId)
|
user = await getUser(userId)
|
||||||
delete user.password
|
delete user.password
|
||||||
authenticated = true
|
authenticated = true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -54,6 +66,7 @@ module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
|
console.error("Auth Error", error)
|
||||||
// remove the cookie as the user does not exist anymore
|
// remove the cookie as the user does not exist anymore
|
||||||
clearCookie(ctx, Cookies.Auth)
|
clearCookie(ctx, Cookies.Auth)
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,26 +75,22 @@ module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const apiKey = ctx.request.headers[Headers.API_KEY]
|
const apiKey = ctx.request.headers[Headers.API_KEY]
|
||||||
const tenantId = ctx.request.headers[Headers.TENANT_ID]
|
|
||||||
// this is an internal request, no user made it
|
// this is an internal request, no user made it
|
||||||
if (!authenticated && apiKey && apiKey === env.INTERNAL_API_KEY) {
|
if (!authenticated && apiKey && apiKey === env.INTERNAL_API_KEY) {
|
||||||
authenticated = true
|
authenticated = true
|
||||||
internal = true
|
internal = true
|
||||||
}
|
}
|
||||||
if (!user && tenantId) {
|
|
||||||
user = { tenantId }
|
|
||||||
}
|
|
||||||
// be explicit
|
// be explicit
|
||||||
if (authenticated !== true) {
|
if (authenticated !== true) {
|
||||||
authenticated = false
|
authenticated = false
|
||||||
}
|
}
|
||||||
// isAuthenticated is a function, so use a variable to be able to check authed state
|
// isAuthenticated is a function, so use a variable to be able to check authed state
|
||||||
finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
|
finalise(ctx, { authenticated, user, internal, version })
|
||||||
return next()
|
return next()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// allow configuring for public access
|
// allow configuring for public access
|
||||||
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
if (opts && opts.publicAllowed) {
|
||||||
finalise(ctx, { authenticated: false, version, publicEndpoint })
|
finalise(ctx, { authenticated: false, version })
|
||||||
} else {
|
} else {
|
||||||
ctx.throw(err.status || 403, err)
|
ctx.throw(err.status || 403, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ const google = require("./passport/google")
|
||||||
const oidc = require("./passport/oidc")
|
const oidc = require("./passport/oidc")
|
||||||
const authenticated = require("./authenticated")
|
const authenticated = require("./authenticated")
|
||||||
const auditLog = require("./auditLog")
|
const auditLog = require("./auditLog")
|
||||||
const tenancy = require("./tenancy")
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
google,
|
google,
|
||||||
|
@ -13,5 +12,4 @@ module.exports = {
|
||||||
local,
|
local,
|
||||||
authenticated,
|
authenticated,
|
||||||
auditLog,
|
auditLog,
|
||||||
tenancy,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g
|
|
||||||
|
|
||||||
exports.buildMatcherRegex = patterns => {
|
|
||||||
if (!patterns) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return patterns.map(pattern => {
|
|
||||||
const isObj = typeof pattern === "object" && pattern.route
|
|
||||||
const method = isObj ? pattern.method : "GET"
|
|
||||||
let route = isObj ? pattern.route : pattern
|
|
||||||
|
|
||||||
const matches = route.match(PARAM_REGEX)
|
|
||||||
if (matches) {
|
|
||||||
for (let match of matches) {
|
|
||||||
const pattern = "/.*" + (match.endsWith("/") ? "/" : "")
|
|
||||||
route = route.replace(match, pattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { regex: new RegExp(route), method }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.matches = (ctx, options) => {
|
|
||||||
return options.find(({ regex, method }) => {
|
|
||||||
const urlMatch = regex.test(ctx.request.url)
|
|
||||||
const methodMatch =
|
|
||||||
method === "ALL"
|
|
||||||
? true
|
|
||||||
: ctx.request.method.toLowerCase() === method.toLowerCase()
|
|
||||||
|
|
||||||
return urlMatch && methodMatch
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -27,13 +27,13 @@ async function authenticate(accessToken, refreshToken, profile, done) {
|
||||||
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
|
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
|
||||||
* @returns Dynamically configured Passport Google Strategy
|
* @returns Dynamically configured Passport Google Strategy
|
||||||
*/
|
*/
|
||||||
exports.strategyFactory = async function (config, callbackUrl) {
|
exports.strategyFactory = async function (config) {
|
||||||
try {
|
try {
|
||||||
const { clientID, clientSecret } = config
|
const { clientID, clientSecret, callbackURL } = config
|
||||||
|
|
||||||
if (!clientID || !clientSecret) {
|
if (!clientID || !clientSecret || !callbackURL) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Configuration invalid. Must contain google clientID and clientSecret"
|
"Configuration invalid. Must contain google clientID, clientSecret and callbackURL"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ exports.strategyFactory = async function (config, callbackUrl) {
|
||||||
{
|
{
|
||||||
clientID: config.clientID,
|
clientID: config.clientID,
|
||||||
clientSecret: config.clientSecret,
|
clientSecret: config.clientSecret,
|
||||||
callbackURL: callbackUrl,
|
callbackURL: config.callbackURL,
|
||||||
},
|
},
|
||||||
authenticate
|
authenticate
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,23 +6,19 @@ const { getGlobalUserByEmail } = require("../../utils")
|
||||||
const { authError } = require("./utils")
|
const { authError } = require("./utils")
|
||||||
const { newid } = require("../../hashing")
|
const { newid } = require("../../hashing")
|
||||||
const { createASession } = require("../../security/sessions")
|
const { createASession } = require("../../security/sessions")
|
||||||
const { getTenantId } = require("../../tenancy")
|
|
||||||
|
|
||||||
const INVALID_ERR = "Invalid Credentials"
|
const INVALID_ERR = "Invalid Credentials"
|
||||||
|
|
||||||
exports.options = {
|
exports.options = {}
|
||||||
passReqToCallback: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Passport Local Authentication Middleware.
|
* Passport Local Authentication Middleware.
|
||||||
* @param {*} ctx the request structure
|
* @param {*} email - username to login with
|
||||||
* @param {*} email username to login with
|
* @param {*} password - plain text password to log in with
|
||||||
* @param {*} password plain text password to log in with
|
* @param {*} done - callback from passport to return user information and errors
|
||||||
* @param {*} done callback from passport to return user information and errors
|
|
||||||
* @returns The authenticated user, or errors if they occur
|
* @returns The authenticated user, or errors if they occur
|
||||||
*/
|
*/
|
||||||
exports.authenticate = async function (ctx, email, password, done) {
|
exports.authenticate = async function (email, password, done) {
|
||||||
if (!email) return authError(done, "Email Required")
|
if (!email) return authError(done, "Email Required")
|
||||||
if (!password) return authError(done, "Password Required")
|
if (!password) return authError(done, "Password Required")
|
||||||
|
|
||||||
|
@ -39,14 +35,12 @@ exports.authenticate = async function (ctx, email, password, done) {
|
||||||
// authenticate
|
// authenticate
|
||||||
if (await compare(password, dbUser.password)) {
|
if (await compare(password, dbUser.password)) {
|
||||||
const sessionId = newid()
|
const sessionId = newid()
|
||||||
const tenantId = getTenantId()
|
await createASession(dbUser._id, sessionId)
|
||||||
await createASession(dbUser._id, { sessionId, tenantId })
|
|
||||||
|
|
||||||
dbUser.token = jwt.sign(
|
dbUser.token = jwt.sign(
|
||||||
{
|
{
|
||||||
userId: dbUser._id,
|
userId: dbUser._id,
|
||||||
sessionId,
|
sessionId,
|
||||||
tenantId,
|
|
||||||
},
|
},
|
||||||
env.JWT_SECRET
|
env.JWT_SECRET
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
const { data } = require("./utilities/mock-data")
|
const { data } = require("./utilities/mock-data")
|
||||||
|
|
||||||
const TENANT_ID = "default"
|
|
||||||
|
|
||||||
const googleConfig = {
|
const googleConfig = {
|
||||||
|
callbackURL: "http://somecallbackurl",
|
||||||
clientID: data.clientID,
|
clientID: data.clientID,
|
||||||
clientSecret: data.clientSecret,
|
clientSecret: data.clientSecret,
|
||||||
}
|
}
|
||||||
|
@ -27,14 +26,13 @@ describe("google", () => {
|
||||||
|
|
||||||
it("should create successfully create a google strategy", async () => {
|
it("should create successfully create a google strategy", async () => {
|
||||||
const google = require("../google")
|
const google = require("../google")
|
||||||
|
|
||||||
const callbackUrl = `/api/global/auth/${TENANT_ID}/google/callback`
|
await google.strategyFactory(googleConfig)
|
||||||
await google.strategyFactory(googleConfig, callbackUrl)
|
|
||||||
|
|
||||||
const expectedOptions = {
|
const expectedOptions = {
|
||||||
clientID: googleConfig.clientID,
|
clientID: googleConfig.clientID,
|
||||||
clientSecret: googleConfig.clientSecret,
|
clientSecret: googleConfig.clientSecret,
|
||||||
callbackURL: callbackUrl,
|
callbackURL: googleConfig.callbackURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(mockStrategy).toHaveBeenCalledWith(
|
expect(mockStrategy).toHaveBeenCalledWith(
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
const { generateGlobalUserID } = require("../../db/utils")
|
const database = require("../../db")
|
||||||
|
const { StaticDatabases, generateGlobalUserID } = require("../../db/utils")
|
||||||
const { authError } = require("./utils")
|
const { authError } = require("./utils")
|
||||||
const { newid } = require("../../hashing")
|
const { newid } = require("../../hashing")
|
||||||
const { createASession } = require("../../security/sessions")
|
const { createASession } = require("../../security/sessions")
|
||||||
const { getGlobalUserByEmail } = require("../../utils")
|
const { getGlobalUserByEmail } = require("../../utils")
|
||||||
const { getGlobalDB, getTenantId } = require("../../tenancy")
|
|
||||||
const fetch = require("node-fetch")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common authentication logic for third parties. e.g. OAuth, OIDC.
|
* Common authentication logic for third parties. e.g. OAuth, OIDC.
|
||||||
|
@ -16,21 +15,19 @@ exports.authenticateThirdParty = async function (
|
||||||
requireLocalAccount = true,
|
requireLocalAccount = true,
|
||||||
done
|
done
|
||||||
) {
|
) {
|
||||||
if (!thirdPartyUser.provider) {
|
if (!thirdPartyUser.provider)
|
||||||
return authError(done, "third party user provider required")
|
return authError(done, "third party user provider required")
|
||||||
}
|
if (!thirdPartyUser.userId)
|
||||||
if (!thirdPartyUser.userId) {
|
|
||||||
return authError(done, "third party user id required")
|
return authError(done, "third party user id required")
|
||||||
}
|
if (!thirdPartyUser.email)
|
||||||
if (!thirdPartyUser.email) {
|
|
||||||
return authError(done, "third party user email required")
|
return authError(done, "third party user email required")
|
||||||
}
|
|
||||||
|
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
||||||
|
|
||||||
|
let dbUser
|
||||||
|
|
||||||
// use the third party id
|
// use the third party id
|
||||||
const userId = generateGlobalUserID(thirdPartyUser.userId)
|
const userId = generateGlobalUserID(thirdPartyUser.userId)
|
||||||
const db = getGlobalDB()
|
|
||||||
|
|
||||||
let dbUser
|
|
||||||
|
|
||||||
// try to load by id
|
// try to load by id
|
||||||
try {
|
try {
|
||||||
|
@ -76,8 +73,7 @@ exports.authenticateThirdParty = async function (
|
||||||
|
|
||||||
// authenticate
|
// authenticate
|
||||||
const sessionId = newid()
|
const sessionId = newid()
|
||||||
const tenantId = getTenantId()
|
await createASession(dbUser._id, sessionId)
|
||||||
await createASession(dbUser._id, { sessionId, tenantId })
|
|
||||||
|
|
||||||
dbUser.token = jwt.sign(
|
dbUser.token = jwt.sign(
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
const { setTenantId } = require("../tenancy")
|
|
||||||
const ContextFactory = require("../tenancy/FunctionContext")
|
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
|
||||||
|
|
||||||
module.exports = (allowQueryStringPatterns, noTenancyPatterns) => {
|
|
||||||
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
|
||||||
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
|
||||||
|
|
||||||
return ContextFactory.getMiddleware(ctx => {
|
|
||||||
const allowNoTenant = !!matches(ctx, noTenancyOptions)
|
|
||||||
const allowQs = !!matches(ctx, allowQsOptions)
|
|
||||||
setTenantId(ctx, { allowQs, allowNoTenant })
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -12,13 +12,12 @@ function makeSessionID(userId, sessionId) {
|
||||||
return `${userId}/${sessionId}`
|
return `${userId}/${sessionId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createASession = async (userId, session) => {
|
exports.createASession = async (userId, sessionId) => {
|
||||||
const client = await redis.getSessionClient()
|
const client = await redis.getSessionClient()
|
||||||
const sessionId = session.sessionId
|
const session = {
|
||||||
session = {
|
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
lastAccessedAt: new Date().toISOString(),
|
lastAccessedAt: new Date().toISOString(),
|
||||||
...session,
|
sessionId,
|
||||||
userId,
|
userId,
|
||||||
}
|
}
|
||||||
await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS)
|
await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS)
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
const cls = require("cls-hooked")
|
|
||||||
const { newid } = require("../hashing")
|
|
||||||
|
|
||||||
const REQUEST_ID_KEY = "requestId"
|
|
||||||
|
|
||||||
class FunctionContext {
|
|
||||||
static getMiddleware(updateCtxFn = null) {
|
|
||||||
const namespace = this.createNamespace()
|
|
||||||
|
|
||||||
return async function (ctx, next) {
|
|
||||||
await new Promise(
|
|
||||||
namespace.bind(function (resolve, reject) {
|
|
||||||
// store a contextual request ID that can be used anywhere (audit logs)
|
|
||||||
namespace.set(REQUEST_ID_KEY, newid())
|
|
||||||
namespace.bindEmitter(ctx.req)
|
|
||||||
namespace.bindEmitter(ctx.res)
|
|
||||||
|
|
||||||
if (updateCtxFn) {
|
|
||||||
updateCtxFn(ctx)
|
|
||||||
}
|
|
||||||
next().then(resolve).catch(reject)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static run(callback) {
|
|
||||||
const namespace = this.createNamespace()
|
|
||||||
|
|
||||||
return namespace.runAndReturn(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
static setOnContext(key, value) {
|
|
||||||
const namespace = this.createNamespace()
|
|
||||||
namespace.set(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getContextStorage() {
|
|
||||||
if (this._namespace && this._namespace.active) {
|
|
||||||
let contextData = this._namespace.active
|
|
||||||
delete contextData.id
|
|
||||||
delete contextData._ns_name
|
|
||||||
return contextData
|
|
||||||
}
|
|
||||||
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromContext(key) {
|
|
||||||
const context = this.getContextStorage()
|
|
||||||
if (context) {
|
|
||||||
return context[key]
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static destroyNamespace() {
|
|
||||||
if (this._namespace) {
|
|
||||||
cls.destroyNamespace("session")
|
|
||||||
this._namespace = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static createNamespace() {
|
|
||||||
if (!this._namespace) {
|
|
||||||
this._namespace = cls.createNamespace("session")
|
|
||||||
}
|
|
||||||
return this._namespace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = FunctionContext
|
|
|
@ -1,81 +0,0 @@
|
||||||
const env = require("../environment")
|
|
||||||
const { Headers } = require("../../constants")
|
|
||||||
const cls = require("./FunctionContext")
|
|
||||||
|
|
||||||
exports.DEFAULT_TENANT_ID = "default"
|
|
||||||
|
|
||||||
exports.isDefaultTenant = () => {
|
|
||||||
return exports.getTenantId() === exports.DEFAULT_TENANT_ID
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.isMultiTenant = () => {
|
|
||||||
return env.MULTI_TENANCY
|
|
||||||
}
|
|
||||||
|
|
||||||
const TENANT_ID = "tenantId"
|
|
||||||
|
|
||||||
// used for automations, API endpoints should always be in context already
|
|
||||||
exports.doInTenant = (tenantId, task) => {
|
|
||||||
return cls.run(() => {
|
|
||||||
// set the tenant id
|
|
||||||
cls.setOnContext(TENANT_ID, tenantId)
|
|
||||||
|
|
||||||
// invoke the task
|
|
||||||
const result = task()
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.updateTenantId = tenantId => {
|
|
||||||
cls.setOnContext(TENANT_ID, tenantId)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.setTenantId = (
|
|
||||||
ctx,
|
|
||||||
opts = { allowQs: false, allowNoTenant: false }
|
|
||||||
) => {
|
|
||||||
let tenantId
|
|
||||||
// exit early if not multi-tenant
|
|
||||||
if (!exports.isMultiTenant()) {
|
|
||||||
cls.setOnContext(TENANT_ID, this.DEFAULT_TENANT_ID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const allowQs = opts && opts.allowQs
|
|
||||||
const allowNoTenant = opts && opts.allowNoTenant
|
|
||||||
const header = ctx.request.headers[Headers.TENANT_ID]
|
|
||||||
const user = ctx.user || {}
|
|
||||||
if (allowQs) {
|
|
||||||
const query = ctx.request.query || {}
|
|
||||||
tenantId = query.tenantId
|
|
||||||
}
|
|
||||||
// override query string (if allowed) by user, or header
|
|
||||||
// URL params cannot be used in a middleware, as they are
|
|
||||||
// processed later in the chain
|
|
||||||
tenantId = user.tenantId || header || tenantId
|
|
||||||
|
|
||||||
if (!tenantId && !allowNoTenant) {
|
|
||||||
ctx.throw(403, "Tenant id not set")
|
|
||||||
}
|
|
||||||
// check tenant ID just incase no tenant was allowed
|
|
||||||
if (tenantId) {
|
|
||||||
cls.setOnContext(TENANT_ID, tenantId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.isTenantIdSet = () => {
|
|
||||||
const tenantId = cls.getFromContext(TENANT_ID)
|
|
||||||
return !!tenantId
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getTenantId = () => {
|
|
||||||
if (!exports.isMultiTenant()) {
|
|
||||||
return exports.DEFAULT_TENANT_ID
|
|
||||||
}
|
|
||||||
const tenantId = cls.getFromContext(TENANT_ID)
|
|
||||||
if (!tenantId) {
|
|
||||||
throw Error("Tenant id not found")
|
|
||||||
}
|
|
||||||
return tenantId
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
...require("./context"),
|
|
||||||
...require("./tenancy"),
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
const { getDB } = require("../db")
|
|
||||||
const { SEPARATOR, StaticDatabases } = require("../db/constants")
|
|
||||||
const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context")
|
|
||||||
const env = require("../environment")
|
|
||||||
|
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
|
||||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
|
||||||
|
|
||||||
exports.addTenantToUrl = url => {
|
|
||||||
const tenantId = getTenantId()
|
|
||||||
|
|
||||||
if (isMultiTenant()) {
|
|
||||||
const char = url.indexOf("?") === -1 ? "?" : "&"
|
|
||||||
url += `${char}tenantId=${tenantId}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.doesTenantExist = async tenantId => {
|
|
||||||
const db = getDB(PLATFORM_INFO_DB)
|
|
||||||
let tenants
|
|
||||||
try {
|
|
||||||
tenants = await db.get(TENANT_DOC)
|
|
||||||
} catch (err) {
|
|
||||||
// if theres an error the doc doesn't exist, no tenants exist
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
tenants &&
|
|
||||||
Array.isArray(tenants.tenantIds) &&
|
|
||||||
tenants.tenantIds.indexOf(tenantId) !== -1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.tryAddTenant = async (tenantId, userId, email) => {
|
|
||||||
const db = getDB(PLATFORM_INFO_DB)
|
|
||||||
const getDoc = async id => {
|
|
||||||
if (!id) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return await db.get(id)
|
|
||||||
} catch (err) {
|
|
||||||
return { _id: id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let [tenants, userIdDoc, emailDoc] = await Promise.all([
|
|
||||||
getDoc(TENANT_DOC),
|
|
||||||
getDoc(userId),
|
|
||||||
getDoc(email),
|
|
||||||
])
|
|
||||||
if (!Array.isArray(tenants.tenantIds)) {
|
|
||||||
tenants = {
|
|
||||||
_id: TENANT_DOC,
|
|
||||||
tenantIds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let promises = []
|
|
||||||
if (userIdDoc) {
|
|
||||||
userIdDoc.tenantId = tenantId
|
|
||||||
promises.push(db.put(userIdDoc))
|
|
||||||
}
|
|
||||||
if (emailDoc) {
|
|
||||||
emailDoc.tenantId = tenantId
|
|
||||||
promises.push(db.put(emailDoc))
|
|
||||||
}
|
|
||||||
if (tenants.tenantIds.indexOf(tenantId) === -1) {
|
|
||||||
tenants.tenantIds.push(tenantId)
|
|
||||||
promises.push(db.put(tenants))
|
|
||||||
}
|
|
||||||
await Promise.all(promises)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getGlobalDB = (tenantId = null) => {
|
|
||||||
// tenant ID can be set externally, for example user API where
|
|
||||||
// new tenants are being created, this may be the case
|
|
||||||
if (!tenantId) {
|
|
||||||
tenantId = getTenantId()
|
|
||||||
}
|
|
||||||
|
|
||||||
let dbName
|
|
||||||
|
|
||||||
if (tenantId === DEFAULT_TENANT_ID) {
|
|
||||||
dbName = StaticDatabases.GLOBAL.name
|
|
||||||
} else {
|
|
||||||
dbName = `${tenantId}${SEPARATOR}${StaticDatabases.GLOBAL.name}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDB(dbName)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.lookupTenantId = async userId => {
|
|
||||||
const db = getDB(StaticDatabases.PLATFORM_INFO.name)
|
|
||||||
let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null
|
|
||||||
try {
|
|
||||||
const doc = await db.get(userId)
|
|
||||||
if (doc && doc.tenantId) {
|
|
||||||
tenantId = doc.tenantId
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// just return the default
|
|
||||||
}
|
|
||||||
return tenantId
|
|
||||||
}
|
|
|
@ -1,9 +1,14 @@
|
||||||
const { DocumentTypes, SEPARATOR, ViewNames } = require("./db/utils")
|
const {
|
||||||
|
DocumentTypes,
|
||||||
|
SEPARATOR,
|
||||||
|
ViewNames,
|
||||||
|
StaticDatabases,
|
||||||
|
} = require("./db/utils")
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
const { options } = require("./middleware/passport/jwt")
|
const { options } = require("./middleware/passport/jwt")
|
||||||
const { createUserEmailView } = require("./db/views")
|
const { createUserEmailView } = require("./db/views")
|
||||||
|
const { getDB } = require("./db")
|
||||||
const { Headers } = require("./constants")
|
const { Headers } = require("./constants")
|
||||||
const { getGlobalDB } = require("./tenancy")
|
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
|
||||||
|
@ -106,7 +111,7 @@ exports.getGlobalUserByEmail = async email => {
|
||||||
if (email == null) {
|
if (email == null) {
|
||||||
throw "Must supply an email address to view"
|
throw "Must supply an email address to view"
|
||||||
}
|
}
|
||||||
const db = getGlobalDB()
|
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||||
try {
|
try {
|
||||||
let users = (
|
let users = (
|
||||||
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
||||||
|
@ -118,7 +123,7 @@ exports.getGlobalUserByEmail = async email => {
|
||||||
return users.length <= 1 ? users[0] : users
|
return users.length <= 1 ? users[0] : users
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err != null && err.name === "not_found") {
|
if (err != null && err.name === "not_found") {
|
||||||
await createUserEmailView(db)
|
await createUserEmailView()
|
||||||
return exports.getGlobalUserByEmail(email)
|
return exports.getGlobalUserByEmail(email)
|
||||||
} else {
|
} else {
|
||||||
throw err
|
throw err
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
module.exports = require("./src/tenancy")
|
|
|
@ -798,13 +798,6 @@ ast-types@0.9.6:
|
||||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
|
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
|
||||||
integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=
|
integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=
|
||||||
|
|
||||||
async-hook-jl@^1.7.6:
|
|
||||||
version "1.7.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68"
|
|
||||||
integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==
|
|
||||||
dependencies:
|
|
||||||
stack-chain "^1.3.7"
|
|
||||||
|
|
||||||
async@~2.1.4:
|
async@~2.1.4:
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
|
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
|
||||||
|
@ -1151,15 +1144,6 @@ clone-buffer@1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
|
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
|
||||||
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
|
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
|
||||||
|
|
||||||
cls-hooked@^4.2.2:
|
|
||||||
version "4.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908"
|
|
||||||
integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==
|
|
||||||
dependencies:
|
|
||||||
async-hook-jl "^1.7.6"
|
|
||||||
emitter-listener "^1.0.1"
|
|
||||||
semver "^5.4.1"
|
|
||||||
|
|
||||||
cluster-key-slot@^1.1.0:
|
cluster-key-slot@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||||
|
@ -1460,13 +1444,6 @@ electron-to-chromium@^1.3.723:
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.775.tgz#046517d1f2cea753e06fff549995b9dc45e20082"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.775.tgz#046517d1f2cea753e06fff549995b9dc45e20082"
|
||||||
integrity sha512-EGuiJW4yBPOTj2NtWGZcX93ZE8IGj33HJAx4d3ouE2zOfW2trbWU+t1e0yzLr1qQIw81++txbM3BH52QwSRE6Q==
|
integrity sha512-EGuiJW4yBPOTj2NtWGZcX93ZE8IGj33HJAx4d3ouE2zOfW2trbWU+t1e0yzLr1qQIw81++txbM3BH52QwSRE6Q==
|
||||||
|
|
||||||
emitter-listener@^1.0.1:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
|
|
||||||
integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==
|
|
||||||
dependencies:
|
|
||||||
shimmer "^1.2.0"
|
|
||||||
|
|
||||||
emittery@^0.7.1:
|
emittery@^0.7.1:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82"
|
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82"
|
||||||
|
@ -4058,7 +4035,7 @@ saxes@^5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
xmlchars "^2.2.0"
|
xmlchars "^2.2.0"
|
||||||
|
|
||||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
|
"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||||
|
@ -4119,11 +4096,6 @@ shellwords@^0.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
|
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
|
||||||
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
|
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
|
||||||
|
|
||||||
shimmer@^1.2.0:
|
|
||||||
version "1.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
|
|
||||||
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
|
|
||||||
|
|
||||||
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
||||||
|
@ -4278,11 +4250,6 @@ sshpk@^1.7.0:
|
||||||
safer-buffer "^2.0.2"
|
safer-buffer "^2.0.2"
|
||||||
tweetnacl "~0.14.0"
|
tweetnacl "~0.14.0"
|
||||||
|
|
||||||
stack-chain@^1.3.7:
|
|
||||||
version "1.3.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285"
|
|
||||||
integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=
|
|
||||||
|
|
||||||
stack-utils@^2.0.2:
|
stack-utils@^2.0.2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277"
|
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277"
|
||||||
|
|
|
@ -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": "0.9.87-alpha.9",
|
"version": "0.9.95",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "0.9.87-alpha.9",
|
"version": "0.9.95",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.87-alpha.9",
|
"@budibase/bbui": "^0.9.95",
|
||||||
"@budibase/client": "^0.9.87-alpha.9",
|
"@budibase/client": "^0.9.95",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^0.9.87-alpha.9",
|
"@budibase/string-templates": "^0.9.95",
|
||||||
"@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",
|
||||||
|
|
|
@ -70,7 +70,7 @@ export const getFrontendStore = () => {
|
||||||
url: application.url,
|
url: application.url,
|
||||||
layouts,
|
layouts,
|
||||||
screens,
|
screens,
|
||||||
theme: application.theme,
|
theme: application.theme || "spectrum--light",
|
||||||
hasAppPackage: true,
|
hasAppPackage: true,
|
||||||
appInstance: application.instance,
|
appInstance: application.instance,
|
||||||
clientLibPath,
|
clientLibPath,
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
$: tempValue = Array.isArray(value) ? value : []
|
|
||||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||||
|
$: tempValue = readableValue
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onChange(tempValue)
|
onChange(tempValue)
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
slot="body"
|
slot="body"
|
||||||
value={readableValue}
|
value={readableValue}
|
||||||
close={handleClose}
|
close={handleClose}
|
||||||
on:update={event => (tempValue = event.detail)}
|
on:change={event => (tempValue = event.detail)}
|
||||||
bindableProperties={bindings}
|
bindableProperties={bindings}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
value={$store.theme || "spectrum--light"}
|
value={$store.theme}
|
||||||
options={themeOptions}
|
options={themeOptions}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
on:change={e => store.actions.theme.save(e.detail)}
|
on:change={e => store.actions.theme.save(e.detail)}
|
||||||
|
|
|
@ -47,6 +47,18 @@ export default `
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse received message
|
||||||
|
// If parsing fails, just ignore and wait for the next message
|
||||||
|
let parsed
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(event.data)
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
if (!parsed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Extract data from message
|
// Extract data from message
|
||||||
const {
|
const {
|
||||||
selectedComponentId,
|
selectedComponentId,
|
||||||
|
@ -55,7 +67,7 @@ export default `
|
||||||
previewType,
|
previewType,
|
||||||
appId,
|
appId,
|
||||||
theme
|
theme
|
||||||
} = JSON.parse(event.data)
|
} = parsed
|
||||||
|
|
||||||
// Set some flags so the app knows we're in the builder
|
// Set some flags so the app knows we're in the builder
|
||||||
window["##BUDIBASE_IN_BUILDER##"] = true
|
window["##BUDIBASE_IN_BUILDER##"] = true
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let assetInstance
|
export let assetInstance
|
||||||
|
export let bindings
|
||||||
|
|
||||||
const layoutDefinition = []
|
const layoutDefinition = []
|
||||||
const screenDefinition = [
|
const screenDefinition = [
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
options: setting.options,
|
options: setting.options,
|
||||||
placeholder: setting.placeholder,
|
placeholder: setting.placeholder,
|
||||||
}}
|
}}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import ConditionalUIDrawer from "./PropertyControls/ConditionalUIDrawer.svelte"
|
import ConditionalUIDrawer from "./PropertyControls/ConditionalUIDrawer.svelte"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
export let bindings
|
||||||
|
|
||||||
let tempValue
|
let tempValue
|
||||||
let drawer
|
let drawer
|
||||||
|
@ -32,5 +33,5 @@
|
||||||
Show, hide and update components in response to conditions being met.
|
Show, hide and update components in response to conditions being met.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
||||||
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} />
|
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} {bindings} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
export let bindings
|
||||||
|
|
||||||
const getStyles = def => {
|
const getStyles = def => {
|
||||||
if (!def?.styles?.length) {
|
if (!def?.styles?.length) {
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
columns={style.columns}
|
columns={style.columns}
|
||||||
properties={style.settings}
|
properties={style.settings}
|
||||||
{componentInstance}
|
{componentInstance}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,27 +1,45 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, selectedComponent } from "builderStore"
|
import { store, selectedComponent, currentAsset } from "builderStore"
|
||||||
import { Tabs, Tab } from "@budibase/bbui"
|
import { Tabs, Tab } from "@budibase/bbui"
|
||||||
import ScreenSettingsSection from "./ScreenSettingsSection.svelte"
|
import ScreenSettingsSection from "./ScreenSettingsSection.svelte"
|
||||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||||
import DesignSection from "./DesignSection.svelte"
|
import DesignSection from "./DesignSection.svelte"
|
||||||
import CustomStylesSection from "./CustomStylesSection.svelte"
|
import CustomStylesSection from "./CustomStylesSection.svelte"
|
||||||
import ConditionalUISection from "./ConditionalUISection.svelte"
|
import ConditionalUISection from "./ConditionalUISection.svelte"
|
||||||
|
import { getBindableProperties } from "builderStore/dataBinding"
|
||||||
|
|
||||||
$: componentInstance = $selectedComponent
|
$: componentInstance = $selectedComponent
|
||||||
$: componentDefinition = store.actions.components.getDefinition(
|
$: componentDefinition = store.actions.components.getDefinition(
|
||||||
$selectedComponent?._component
|
$selectedComponent?._component
|
||||||
)
|
)
|
||||||
|
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tabs selected="Settings" noPadding>
|
<Tabs selected="Settings" noPadding>
|
||||||
<Tab title="Settings">
|
<Tab title="Settings">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#key componentInstance?._id}
|
{#key componentInstance?._id}
|
||||||
<ScreenSettingsSection {componentInstance} {componentDefinition} />
|
<ScreenSettingsSection
|
||||||
<ComponentSettingsSection {componentInstance} {componentDefinition} />
|
{componentInstance}
|
||||||
<DesignSection {componentInstance} {componentDefinition} />
|
{componentDefinition}
|
||||||
<CustomStylesSection {componentInstance} {componentDefinition} />
|
{bindings}
|
||||||
<ConditionalUISection {componentInstance} {componentDefinition} />
|
/>
|
||||||
|
<ComponentSettingsSection
|
||||||
|
{componentInstance}
|
||||||
|
{componentDefinition}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
|
<DesignSection {componentInstance} {componentDefinition} {bindings} />
|
||||||
|
<CustomStylesSection
|
||||||
|
{componentInstance}
|
||||||
|
{componentDefinition}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
|
<ConditionalUISection
|
||||||
|
{componentInstance}
|
||||||
|
{componentDefinition}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
|
@ -13,12 +13,12 @@
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
import { selectedComponent, store } from "builderStore"
|
||||||
import { currentAsset, selectedComponent, store } from "builderStore"
|
|
||||||
import { getComponentForSettingType } from "./componentSettings"
|
import { getComponentForSettingType } from "./componentSettings"
|
||||||
import PropertyControl from "./PropertyControl.svelte"
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
|
|
||||||
export let conditions = []
|
export let conditions = []
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
const flipDurationMs = 150
|
const flipDurationMs = 150
|
||||||
const actionOptions = [
|
const actionOptions = [
|
||||||
|
@ -64,10 +64,6 @@
|
||||||
value: setting.key,
|
value: setting.key,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
$: bindableProperties = getBindableProperties(
|
|
||||||
$currentAsset,
|
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
$: conditions.forEach(link => {
|
$: conditions.forEach(link => {
|
||||||
if (!link.id) {
|
if (!link.id) {
|
||||||
link.id = generate()
|
link.id = generate()
|
||||||
|
@ -194,6 +190,7 @@
|
||||||
placeholder: getSettingDefinition(condition.setting)
|
placeholder: getSettingDefinition(condition.setting)
|
||||||
.placeholder,
|
.placeholder,
|
||||||
}}
|
}}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Select disabled placeholder=" " />
|
<Select disabled placeholder=" " />
|
||||||
|
@ -201,7 +198,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<div>IF</div>
|
<div>IF</div>
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
bindings={bindableProperties}
|
{bindings}
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
value={condition.newValue}
|
value={condition.newValue}
|
||||||
on:change={e => (condition.newValue = e.detail)}
|
on:change={e => (condition.newValue = e.detail)}
|
||||||
|
@ -222,7 +219,7 @@
|
||||||
{#if ["string", "number"].includes(condition.valueType)}
|
{#if ["string", "number"].includes(condition.valueType)}
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
disabled={condition.noValue}
|
disabled={condition.noValue}
|
||||||
bindings={bindableProperties}
|
{bindings}
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
value={condition.referenceValue}
|
value={condition.referenceValue}
|
||||||
on:change={e => (condition.referenceValue = e.detail)}
|
on:change={e => (condition.referenceValue = e.detail)}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { getDataProviderComponents } from "builderStore/dataBinding"
|
||||||
getBindableProperties,
|
|
||||||
getDataProviderComponents,
|
|
||||||
} from "builderStore/dataBinding"
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
|
@ -31,6 +28,7 @@
|
||||||
export let value = {}
|
export let value = {}
|
||||||
export let otherSources
|
export let otherSources
|
||||||
export let showAllQueries
|
export let showAllQueries
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
$: text = value?.label ?? "Choose an option"
|
$: text = value?.label ?? "Choose an option"
|
||||||
$: tables = $tablesStore.list.map(m => ({
|
$: tables = $tablesStore.list.map(m => ({
|
||||||
|
@ -60,10 +58,6 @@
|
||||||
parameters: query.parameters,
|
parameters: query.parameters,
|
||||||
type: "query",
|
type: "query",
|
||||||
}))
|
}))
|
||||||
$: bindableProperties = getBindableProperties(
|
|
||||||
$currentAsset,
|
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
$: dataProviders = getDataProviderComponents(
|
$: dataProviders = getDataProviderComponents(
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
$store.selectedComponentId
|
$store.selectedComponentId
|
||||||
|
@ -75,13 +69,13 @@
|
||||||
type: "provider",
|
type: "provider",
|
||||||
schema: provider.schema,
|
schema: provider.schema,
|
||||||
}))
|
}))
|
||||||
$: queryBindableProperties = bindableProperties.map(property => ({
|
$: queryBindableProperties = bindings.map(property => ({
|
||||||
...property,
|
...property,
|
||||||
category: property.type === "instance" ? "Component" : "Table",
|
category: property.type === "instance" ? "Component" : "Table",
|
||||||
label: property.readableBinding,
|
label: property.readableBinding,
|
||||||
path: property.readableBinding,
|
path: property.readableBinding,
|
||||||
}))
|
}))
|
||||||
$: links = bindableProperties
|
$: links = bindings
|
||||||
.filter(x => x.fieldSchema?.type === "link")
|
.filter(x => x.fieldSchema?.type === "link")
|
||||||
.map(property => {
|
.map(property => {
|
||||||
return {
|
return {
|
||||||
|
@ -138,7 +132,7 @@
|
||||||
bind:customParams={value.queryParams}
|
bind:customParams={value.queryParams}
|
||||||
parameters={queries.find(query => query._id === value._id)
|
parameters={queries.find(query => query._id === value._id)
|
||||||
.parameters}
|
.parameters}
|
||||||
bindings={queryBindableProperties}
|
{bindings}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<IntegrationQueryEditor
|
<IntegrationQueryEditor
|
||||||
|
|
|
@ -13,10 +13,10 @@
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
|
||||||
const flipDurationMs = 150
|
const flipDurationMs = 150
|
||||||
|
|
||||||
const EVENT_TYPE_KEY = "##eventHandlerType"
|
const EVENT_TYPE_KEY = "##eventHandlerType"
|
||||||
|
|
||||||
export let actions
|
export let actions
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
// dndzone needs an id on the array items, so this adds some temporary ones.
|
// dndzone needs an id on the array items, so this adds some temporary ones.
|
||||||
$: {
|
$: {
|
||||||
|
@ -121,6 +121,7 @@
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={selectedActionComponent}
|
this={selectedActionComponent}
|
||||||
parameters={selectedAction.parameters}
|
parameters={selectedAction.parameters}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
export let value = []
|
export let value = []
|
||||||
export let name
|
export let name
|
||||||
|
export let bindings
|
||||||
|
|
||||||
let drawer
|
let drawer
|
||||||
|
|
||||||
|
@ -57,5 +58,5 @@
|
||||||
Define what actions to run.
|
Define what actions to run.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<Button cta slot="buttons" on:click={saveEventData}>Save</Button>
|
<Button cta slot="buttons" on:click={saveEventData}>Save</Button>
|
||||||
<EventEditor slot="body" bind:actions={value} eventType={name} />
|
<EventEditor slot="body" bind:actions={value} eventType={name} {bindings} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Checkbox, Input } from "@budibase/bbui"
|
import { Select, Label, Checkbox, Input } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
$: tableOptions = $tables.list || []
|
$: tableOptions = $tables.list || []
|
||||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Layout, Input, Checkbox } from "@budibase/bbui"
|
import { Select, Layout, Input, Checkbox } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
|
||||||
import { datasources, integrations, queries } from "stores/backend"
|
import { datasources, integrations, queries } from "stores/backend"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
|
||||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
$: query = $queries.list.find(q => q._id === parameters.queryId)
|
$: query = $queries.list.find(q => q._id === parameters.queryId)
|
||||||
$: datasource = $datasources.list.find(
|
$: datasource = $datasources.list.find(
|
||||||
ds => ds._id === parameters.datasourceId
|
ds => ds._id === parameters.datasourceId
|
||||||
)
|
)
|
||||||
$: bindableProperties = getBindableProperties(
|
|
||||||
$currentAsset,
|
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
|
|
||||||
function fetchQueryDefinition(query) {
|
function fetchQueryDefinition(query) {
|
||||||
const source = $datasources.list.find(
|
const source = $datasources.list.find(
|
||||||
|
@ -61,7 +56,7 @@
|
||||||
<ParameterBuilder
|
<ParameterBuilder
|
||||||
bind:customParams={parameters.queryParams}
|
bind:customParams={parameters.queryParams}
|
||||||
parameters={query.parameters}
|
parameters={query.parameters}
|
||||||
bindings={bindableProperties}
|
{bindings}
|
||||||
/>
|
/>
|
||||||
<IntegrationQueryEditor
|
<IntegrationQueryEditor
|
||||||
height={200}
|
height={200}
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, Checkbox } from "@budibase/bbui"
|
import { Label } from "@budibase/bbui"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
|
||||||
import { currentAsset, store } from "builderStore"
|
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
export let bindings = []
|
||||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, ActionButton, Button, Select, Input } from "@budibase/bbui"
|
import { Label, ActionButton, Button, Select, Input } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
|
@ -11,13 +9,10 @@
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let fieldLabel = "Column"
|
export let fieldLabel = "Column"
|
||||||
export let valueLabel = "Value"
|
export let valueLabel = "Value"
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
let fields = Object.entries(parameterFields || {})
|
let fields = Object.entries(parameterFields || {})
|
||||||
$: onChange(fields)
|
$: onChange(fields)
|
||||||
$: bindableProperties = getBindableProperties(
|
|
||||||
$currentAsset,
|
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
|
|
||||||
const addField = () => {
|
const addField = () => {
|
||||||
fields = [...fields.filter(field => field[0]), ["", ""]]
|
fields = [...fields.filter(field => field[0]), ["", ""]]
|
||||||
|
@ -69,7 +64,7 @@
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
title={`Value for "${field[0]}"`}
|
title={`Value for "${field[0]}"`}
|
||||||
value={field[1]}
|
value={field[1]}
|
||||||
bindings={bindableProperties}
|
{bindings}
|
||||||
on:change={event => updateFieldValue(idx, event.detail)}
|
on:change={event => updateFieldValue(idx, event.detail)}
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
$: dataProviderComponents = getDataProviderComponents(
|
$: dataProviderComponents = getDataProviderComponents(
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
|
@ -70,6 +71,7 @@
|
||||||
parameterFields={parameters.fields}
|
parameterFields={parameters.fields}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
on:change={onFieldsChanged}
|
on:change={onFieldsChanged}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
|
|
||||||
|
export let parameters = {}
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
const AUTOMATION_STATUS = {
|
const AUTOMATION_STATUS = {
|
||||||
NEW: "new",
|
NEW: "new",
|
||||||
EXISTING: "existing",
|
EXISTING: "existing",
|
||||||
}
|
}
|
||||||
|
|
||||||
export let parameters = {}
|
|
||||||
|
|
||||||
let automationStatus = parameters.automationId
|
let automationStatus = parameters.automationId
|
||||||
? AUTOMATION_STATUS.EXISTING
|
? AUTOMATION_STATUS.EXISTING
|
||||||
: AUTOMATION_STATUS.NEW
|
: AUTOMATION_STATUS.NEW
|
||||||
|
@ -109,6 +110,7 @@
|
||||||
parameterFields={parameters.fields}
|
parameterFields={parameters.fields}
|
||||||
fieldLabel="Field"
|
fieldLabel="Field"
|
||||||
on:change={onFieldsChanged}
|
on:change={onFieldsChanged}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
export let value = []
|
export let value = []
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
let drawer
|
let drawer
|
||||||
let tempValue = value || []
|
let tempValue = value || []
|
||||||
|
|
||||||
|
@ -51,7 +53,7 @@
|
||||||
constraints.
|
constraints.
|
||||||
{/if}
|
{/if}
|
||||||
</Body>
|
</Body>
|
||||||
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} />
|
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} {bindings} />
|
||||||
</Layout>
|
</Layout>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -7,20 +7,15 @@
|
||||||
Combobox,
|
Combobox,
|
||||||
Input,
|
Input,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
import { OperatorOptions, getValidOperatorsForType } from "helpers/lucene"
|
||||||
|
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let value
|
export let value
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
const BannedTypes = ["link", "attachment"]
|
const BannedTypes = ["link", "attachment"]
|
||||||
$: bindableProperties = getBindableProperties(
|
|
||||||
$currentAsset,
|
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
$: fieldOptions = (schemaFields ?? [])
|
$: fieldOptions = (schemaFields ?? [])
|
||||||
.filter(field => !BannedTypes.includes(field.type))
|
.filter(field => !BannedTypes.includes(field.type))
|
||||||
.map(field => field.name)
|
.map(field => field.name)
|
||||||
|
@ -101,7 +96,7 @@
|
||||||
title={`Value for "${expression.field}"`}
|
title={`Value for "${expression.field}"`}
|
||||||
value={expression.value}
|
value={expression.value}
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
bindings={bindableProperties}
|
{bindings}
|
||||||
on:change={event => (expression.value = event.detail)}
|
on:change={event => (expression.value = event.detail)}
|
||||||
/>
|
/>
|
||||||
{:else if ["string", "longform", "number"].includes(expression.type)}
|
{:else if ["string", "longform", "number"].includes(expression.type)}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Icon, Drawer, Label } from "@budibase/bbui"
|
import { Button, Icon, Drawer, Label } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
|
||||||
import {
|
import {
|
||||||
getBindableProperties,
|
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
|
@ -18,18 +16,15 @@
|
||||||
export let value = null
|
export let value = null
|
||||||
export let props = {}
|
export let props = {}
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
let anchor
|
let anchor
|
||||||
let valid
|
let valid
|
||||||
|
|
||||||
$: bindableProperties = getBindableProperties(
|
$: safeValue = getSafeValue(value, props.defaultValue, bindings)
|
||||||
$currentAsset,
|
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
$: safeValue = getSafeValue(value, props.defaultValue, bindableProperties)
|
|
||||||
$: tempValue = safeValue
|
$: tempValue = safeValue
|
||||||
$: replaceBindings = val => readableToRuntimeBinding(bindableProperties, val)
|
$: replaceBindings = val => readableToRuntimeBinding(bindings, val)
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
handleChange(tempValue)
|
handleChange(tempValue)
|
||||||
|
@ -61,8 +56,8 @@
|
||||||
|
|
||||||
// The "safe" value is the value with any bindings made readable
|
// The "safe" value is the value with any bindings made readable
|
||||||
// If there is no value set, any default value is used
|
// If there is no value set, any default value is used
|
||||||
const getSafeValue = (value, defaultValue, bindableProperties) => {
|
const getSafeValue = (value, defaultValue, bindings) => {
|
||||||
const enriched = runtimeToReadableBinding(bindableProperties, value)
|
const enriched = runtimeToReadableBinding(bindings, value)
|
||||||
return enriched == null && defaultValue !== undefined
|
return enriched == null && defaultValue !== undefined
|
||||||
? defaultValue
|
? defaultValue
|
||||||
: enriched
|
: enriched
|
||||||
|
@ -83,6 +78,7 @@
|
||||||
updateOnChange={false}
|
updateOnChange={false}
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
{bindings}
|
||||||
name={key}
|
name={key}
|
||||||
text={label}
|
text={label}
|
||||||
{type}
|
{type}
|
||||||
|
@ -108,7 +104,7 @@
|
||||||
bind:valid
|
bind:valid
|
||||||
value={safeValue}
|
value={safeValue}
|
||||||
on:change={e => (tempValue = e.detail)}
|
on:change={e => (tempValue = e.detail)}
|
||||||
{bindableProperties}
|
bindableProperties={bindings}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
|
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
export let bindings
|
||||||
|
|
||||||
$: urlOptions = $store.screens
|
$: urlOptions = $store.screens
|
||||||
.map(screen => screen.routing?.route)
|
.map(screen => screen.routing?.route)
|
||||||
.filter(x => x != null)
|
.filter(x => x != null)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DrawerBindableCombobox {value} on:change options={urlOptions} />
|
<DrawerBindableCombobox {value} {bindings} on:change options={urlOptions} />
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import { FrontendTypes } from "constants"
|
import { FrontendTypes } from "constants"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
export let bindings
|
||||||
|
|
||||||
function setAssetProps(name, value) {
|
function setAssetProps(name, value) {
|
||||||
const selectedAsset = get(currentAsset)
|
const selectedAsset = get(currentAsset)
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
key={def.key}
|
key={def.key}
|
||||||
value={deepGet($currentAsset, def.key)}
|
value={deepGet($currentAsset, def.key)}
|
||||||
onChange={val => setAssetProps(def.key, val)}
|
onChange={val => setAssetProps(def.key, val)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
export let columns
|
export let columns
|
||||||
export let properties
|
export let properties
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
$: style = componentInstance._styles.normal || {}
|
$: style = componentInstance._styles.normal || {}
|
||||||
$: changed = properties?.some(prop => hasPropChanged(style, prop)) ?? false
|
$: changed = properties?.some(prop => hasPropChanged(style, prop)) ?? false
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
value={style[prop.key]}
|
value={style[prop.key]}
|
||||||
onChange={val => store.actions.components.updateStyle(prop.key, val)}
|
onChange={val => store.actions.components.updateStyle(prop.key, val)}
|
||||||
props={getControlProps(prop)}
|
props={getControlProps(prop)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -4,10 +4,7 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
|
||||||
$: hasAdminUser = !!$admin?.checklist?.adminUser
|
$: hasAdminUser = !!$admin?.checklist?.adminUser
|
||||||
$: tenantSet = $auth.tenantSet
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await admin.init()
|
await admin.init()
|
||||||
|
@ -15,14 +12,9 @@
|
||||||
loaded = true
|
loaded = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Force creation of an admin user if one doesn't exist
|
||||||
$: {
|
$: {
|
||||||
const apiReady = $admin.loaded && $auth.loaded
|
if (loaded && !hasAdminUser) {
|
||||||
// if tenant is not set go to it
|
|
||||||
if (loaded && apiReady && multiTenancyEnabled && !tenantSet) {
|
|
||||||
$redirect("./auth/org")
|
|
||||||
}
|
|
||||||
// Force creation of an admin user if one doesn't exist
|
|
||||||
else if (loaded && apiReady && !hasAdminUser) {
|
|
||||||
$redirect("./admin")
|
$redirect("./admin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +29,7 @@
|
||||||
!$isActive("./invite")
|
!$isActive("./invite")
|
||||||
) {
|
) {
|
||||||
const returnUrl = encodeURIComponent(window.location.pathname)
|
const returnUrl = encodeURIComponent(window.location.pathname)
|
||||||
$redirect("./auth?", { returnUrl })
|
$redirect("./auth/login?", { returnUrl })
|
||||||
} else if ($auth?.user?.forceResetPassword) {
|
} else if ($auth?.user?.forceResetPassword) {
|
||||||
$redirect("./auth/reset")
|
$redirect("./auth/reset")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,25 +6,20 @@
|
||||||
Layout,
|
Layout,
|
||||||
Input,
|
Input,
|
||||||
Body,
|
Body,
|
||||||
ActionButton,
|
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import { admin, auth } from "stores/portal"
|
import { admin } from "stores/portal"
|
||||||
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
|
||||||
let adminUser = {}
|
let adminUser = {}
|
||||||
let error
|
let error
|
||||||
|
|
||||||
$: tenantId = $auth.tenantId
|
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
try {
|
try {
|
||||||
adminUser.tenantId = tenantId
|
|
||||||
// Save the admin user
|
// Save the admin user
|
||||||
const response = await api.post(`/api/global/users/init`, adminUser)
|
const response = await api.post(`/api/admin/users/init`, adminUser)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error(json.message)
|
throw new Error(json.message)
|
||||||
|
@ -52,22 +47,9 @@
|
||||||
<Input label="Email" bind:value={adminUser.email} />
|
<Input label="Email" bind:value={adminUser.email} />
|
||||||
<PasswordRepeatInput bind:password={adminUser.password} bind:error />
|
<PasswordRepeatInput bind:password={adminUser.password} bind:error />
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="XS" noPadding>
|
<Button cta disabled={error} on:click={save}>
|
||||||
<Button cta disabled={error} on:click={save}>
|
Create super admin user
|
||||||
Create super admin user
|
</Button>
|
||||||
</Button>
|
|
||||||
{#if multiTenancyEnabled}
|
|
||||||
<ActionButton
|
|
||||||
quiet
|
|
||||||
on:click={() => {
|
|
||||||
admin.unload()
|
|
||||||
$goto("../auth/org")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Change organisation
|
|
||||||
</ActionButton>
|
|
||||||
{/if}
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -158,10 +158,16 @@
|
||||||
fieldName: fromTable.primary[0],
|
fieldName: fromTable.primary[0],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// the relateFrom.fieldName should remain the same, as it is the foreignKey in the other
|
||||||
|
// table, this is due to the way that budibase represents relationships, the fieldName in a
|
||||||
|
// link column schema is the column linked to (FK in this case). The foreignKey column is
|
||||||
|
// essentially what is linked to in the from table, this is unique to SQL as this isn't a feature
|
||||||
|
// of Budibase internal tables.
|
||||||
|
// Essentially this means the fieldName is what we are linking to in the other table, and the
|
||||||
|
// foreignKey is what is linking out of the current table.
|
||||||
relateFrom = {
|
relateFrom = {
|
||||||
...relateFrom,
|
...relateFrom,
|
||||||
foreignKey: relateFrom.fieldName,
|
foreignKey: fromTable.primary[0],
|
||||||
fieldName: fromTable.primary[0],
|
|
||||||
}
|
}
|
||||||
relateTo = {
|
relateTo = {
|
||||||
...relateTo,
|
...relateTo,
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { ActionButton } from "@budibase/bbui"
|
||||||
import GoogleLogo from "assets/google-logo.png"
|
import GoogleLogo from "assets/google-logo.png"
|
||||||
import { auth, organisation } from "stores/portal"
|
import { organisation } from "stores/portal"
|
||||||
|
|
||||||
let show
|
|
||||||
|
|
||||||
$: tenantId = $auth.tenantId
|
|
||||||
$: show = $organisation.google
|
$: show = $organisation.google
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
on:click={() =>
|
on:click={() => window.open("/api/admin/auth/google", "_blank")}
|
||||||
window.open(`/api/global/auth/${tenantId}/google`, "_blank")}
|
|
||||||
>
|
>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<img src={GoogleLogo} alt="google icon" />
|
<img src={GoogleLogo} alt="google icon" />
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
{#if show}
|
{#if show}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
on:click={() =>
|
on:click={() =>
|
||||||
window.open(`/api/global/auth/oidc/configs/${$oidc.uuid}`, "_blank")}
|
window.open(`/api/admin/auth/oidc/configs/${$oidc.uuid}`, "_blank")}
|
||||||
>
|
>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<img {src} alt="oidc icon" />
|
<img {src} alt="oidc icon" />
|
||||||
|
|
|
@ -6,12 +6,10 @@
|
||||||
Layout,
|
Layout,
|
||||||
Body,
|
Body,
|
||||||
Heading,
|
Heading,
|
||||||
ActionButton,
|
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { organisation, auth } from "stores/portal"
|
import { organisation, auth } from "stores/portal"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { goto } from "@roxi/routify"
|
|
||||||
|
|
||||||
let email = ""
|
let email = ""
|
||||||
|
|
||||||
|
@ -43,12 +41,9 @@
|
||||||
</Body>
|
</Body>
|
||||||
<Input label="Email" bind:value={email} />
|
<Input label="Email" bind:value={email} />
|
||||||
</Layout>
|
</Layout>
|
||||||
<Layout gap="XS" nopadding>
|
<Button cta on:click={forgot} disabled={!email}>
|
||||||
<Button cta on:click={forgot} disabled={!email}>
|
Reset your password
|
||||||
Reset your password
|
</Button>
|
||||||
</Button>
|
|
||||||
<ActionButton quiet on:click={() => $goto("../")}>Back</ActionButton>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,24 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
import { auth, admin } from "stores/portal"
|
$redirect("./login")
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
$: tenantSet = $auth.tenantSet
|
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
|
||||||
|
|
||||||
let loaded = false
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if (loaded && multiTenancyEnabled && !tenantSet) {
|
|
||||||
$redirect("./org")
|
|
||||||
} else if (loaded) {
|
|
||||||
$redirect("./login")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
await admin.init()
|
|
||||||
await auth.checkQueryString()
|
|
||||||
loaded = true
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { auth, organisation, oidc, admin } from "stores/portal"
|
import { auth, organisation, oidc } from "stores/portal"
|
||||||
import GoogleButton from "./_components/GoogleButton.svelte"
|
import GoogleButton from "./_components/GoogleButton.svelte"
|
||||||
import OIDCButton from "./_components/OIDCButton.svelte"
|
import OIDCButton from "./_components/OIDCButton.svelte"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
@ -18,10 +18,8 @@
|
||||||
|
|
||||||
let username = ""
|
let username = ""
|
||||||
let password = ""
|
let password = ""
|
||||||
let loaded = false
|
|
||||||
|
|
||||||
$: company = $organisation.company || "Budibase"
|
$: company = $organisation.company || "Budibase"
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
try {
|
try {
|
||||||
|
@ -29,6 +27,7 @@
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
|
notifications.success("Logged in successfully")
|
||||||
if ($auth?.user?.forceResetPassword) {
|
if ($auth?.user?.forceResetPassword) {
|
||||||
$goto("./reset")
|
$goto("./reset")
|
||||||
} else {
|
} else {
|
||||||
|
@ -51,7 +50,6 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
loaded = true
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -63,10 +61,8 @@
|
||||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
||||||
<Heading>Sign in to {company}</Heading>
|
<Heading>Sign in to {company}</Heading>
|
||||||
</Layout>
|
</Layout>
|
||||||
{#if loaded}
|
<GoogleButton />
|
||||||
<GoogleButton />
|
<OIDCButton oidcIcon={$oidc.logo} oidcName={$oidc.name} />
|
||||||
<OIDCButton oidcIcon={$oidc.logo} oidcName={$oidc.name} />
|
|
||||||
{/if}
|
|
||||||
<Divider noGrid />
|
<Divider noGrid />
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<Body size="S" textAlign="center">Sign in with email</Body>
|
<Body size="S" textAlign="center">Sign in with email</Body>
|
||||||
|
@ -83,17 +79,6 @@
|
||||||
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
<ActionButton quiet on:click={() => $goto("./forgot")}>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
{#if multiTenancyEnabled}
|
|
||||||
<ActionButton
|
|
||||||
quiet
|
|
||||||
on:click={() => {
|
|
||||||
admin.unload()
|
|
||||||
$goto("./org")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Change organisation
|
|
||||||
</ActionButton>
|
|
||||||
{/if}
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Button, Heading, Input, Layout } from "@budibase/bbui"
|
|
||||||
import { goto } from "@roxi/routify"
|
|
||||||
import { auth, admin } from "stores/portal"
|
|
||||||
import Logo from "assets/bb-emblem.svg"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
let tenantId = get(auth).tenantSet ? get(auth).tenantId : ""
|
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
|
||||||
|
|
||||||
async function setOrg() {
|
|
||||||
if (tenantId == null || tenantId === "") {
|
|
||||||
tenantId = "default"
|
|
||||||
}
|
|
||||||
await auth.setOrg(tenantId)
|
|
||||||
// re-init now org selected
|
|
||||||
await admin.init()
|
|
||||||
$goto("../")
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeydown(evt) {
|
|
||||||
if (evt.key === "Enter") setOrg()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
await auth.checkQueryString()
|
|
||||||
if (!multiTenancyEnabled) {
|
|
||||||
$goto("../")
|
|
||||||
} else {
|
|
||||||
admin.unload()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} />
|
|
||||||
<div class="login">
|
|
||||||
<div class="main">
|
|
||||||
<Layout>
|
|
||||||
<Layout noPadding justifyItems="center">
|
|
||||||
<img alt="logo" src={Logo} />
|
|
||||||
<Heading>Set Budibase organisation</Heading>
|
|
||||||
</Layout>
|
|
||||||
<Layout gap="XS" noPadding>
|
|
||||||
<Input label="Organisation" bind:value={tenantId} />
|
|
||||||
</Layout>
|
|
||||||
<Layout gap="XS" noPadding>
|
|
||||||
<Button cta on:click={setOrg}>Set organisation</Button>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.login {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 48px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -2,15 +2,13 @@
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
import { auth } from "stores/portal"
|
import { auth } from "stores/portal"
|
||||||
|
|
||||||
auth.checkQueryString()
|
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (!$auth.user) {
|
if (!$auth.user) {
|
||||||
$redirect(`./auth`)
|
$redirect("./auth/login")
|
||||||
} else if ($auth.user.builder?.global) {
|
} else if ($auth.user.builder?.global) {
|
||||||
$redirect(`./portal`)
|
$redirect("./portal")
|
||||||
} else {
|
} else {
|
||||||
$redirect(`./apps`)
|
$redirect("./apps")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import OneLoginLogo from "assets/onelogin-logo.png"
|
import OneLoginLogo from "assets/onelogin-logo.png"
|
||||||
import OidcLogoPng from "assets/oidc-logo.png"
|
import OidcLogoPng from "assets/oidc-logo.png"
|
||||||
import { isEqual, cloneDeep } from "lodash/fp"
|
import { isEqual, cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Heading,
|
Heading,
|
||||||
|
@ -21,51 +22,36 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import { organisation, auth, admin } from "stores/portal"
|
import { organisation } from "stores/portal"
|
||||||
import { uuid } from "builderStore/uuid"
|
import { uuid } from "builderStore/uuid"
|
||||||
|
|
||||||
$: tenantId = $auth.tenantId
|
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
|
||||||
|
|
||||||
const ConfigTypes = {
|
const ConfigTypes = {
|
||||||
Google: "google",
|
Google: "google",
|
||||||
OIDC: "oidc",
|
OIDC: "oidc",
|
||||||
|
// Github: "github",
|
||||||
|
// AzureAD: "ad",
|
||||||
}
|
}
|
||||||
|
|
||||||
function callbackUrl(tenantId, end) {
|
const GoogleConfigFields = {
|
||||||
let url = `/api/global/auth`
|
Google: ["clientID", "clientSecret", "callbackURL"],
|
||||||
if (multiTenancyEnabled && tenantId) {
|
}
|
||||||
url += `/${tenantId}`
|
const GoogleConfigLabels = {
|
||||||
}
|
Google: {
|
||||||
url += end
|
clientID: "Client ID",
|
||||||
return url
|
clientSecret: "Client secret",
|
||||||
|
callbackURL: "Callback URL",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
$: GoogleConfigFields = {
|
const OIDCConfigFields = {
|
||||||
Google: [
|
Oidc: ["configUrl", "clientID", "clientSecret"],
|
||||||
{ name: "clientID", label: "Client ID" },
|
|
||||||
{ name: "clientSecret", label: "Client secret" },
|
|
||||||
{
|
|
||||||
name: "callbackURL",
|
|
||||||
label: "Callback URL",
|
|
||||||
readonly: true,
|
|
||||||
placeholder: callbackUrl(tenantId, "/google/callback"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
const OIDCConfigLabels = {
|
||||||
$: OIDCConfigFields = {
|
Oidc: {
|
||||||
Oidc: [
|
configUrl: "Config URL",
|
||||||
{ name: "configUrl", label: "Config URL" },
|
clientID: "Client ID",
|
||||||
{ name: "clientID", label: "Client ID" },
|
clientSecret: "Client Secret",
|
||||||
{ name: "clientSecret", label: "Client Secret" },
|
},
|
||||||
{
|
|
||||||
name: "callbackURL",
|
|
||||||
label: "Callback URL",
|
|
||||||
readonly: true,
|
|
||||||
placeholder: callbackUrl(tenantId, "/oidc/callback"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconDropdownOptions = [
|
let iconDropdownOptions = [
|
||||||
|
@ -123,13 +109,17 @@
|
||||||
|
|
||||||
// Create a flag so that it will only try to save completed forms
|
// Create a flag so that it will only try to save completed forms
|
||||||
$: partialGoogle =
|
$: partialGoogle =
|
||||||
providers.google?.config?.clientID || providers.google?.config?.clientSecret
|
providers.google?.config?.clientID ||
|
||||||
|
providers.google?.config?.clientSecret ||
|
||||||
|
providers.google?.config?.callbackURL
|
||||||
$: partialOidc =
|
$: partialOidc =
|
||||||
providers.oidc?.config?.configs[0].configUrl ||
|
providers.oidc?.config?.configs[0].configUrl ||
|
||||||
providers.oidc?.config?.configs[0].clientID ||
|
providers.oidc?.config?.configs[0].clientID ||
|
||||||
providers.oidc?.config?.configs[0].clientSecret
|
providers.oidc?.config?.configs[0].clientSecret
|
||||||
$: googleComplete =
|
$: googleComplete =
|
||||||
providers.google?.config?.clientID && providers.google?.config?.clientSecret
|
providers.google?.config?.clientID &&
|
||||||
|
providers.google?.config?.clientSecret &&
|
||||||
|
providers.google?.config?.callbackURL
|
||||||
$: oidcComplete =
|
$: oidcComplete =
|
||||||
providers.oidc?.config?.configs[0].configUrl &&
|
providers.oidc?.config?.configs[0].configUrl &&
|
||||||
providers.oidc?.config?.configs[0].clientID &&
|
providers.oidc?.config?.configs[0].clientID &&
|
||||||
|
@ -139,7 +129,7 @@
|
||||||
let data = new FormData()
|
let data = new FormData()
|
||||||
data.append("file", file)
|
data.append("file", file)
|
||||||
const res = await api.post(
|
const res = await api.post(
|
||||||
`/api/global/configs/upload/logos_oidc/${file.name}`,
|
`/api/admin/configs/upload/logos_oidc/${file.name}`,
|
||||||
data,
|
data,
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
@ -159,21 +149,17 @@
|
||||||
let calls = []
|
let calls = []
|
||||||
docs.forEach(element => {
|
docs.forEach(element => {
|
||||||
if (element.type === ConfigTypes.OIDC) {
|
if (element.type === ConfigTypes.OIDC) {
|
||||||
//Add a UUID here so each config is distinguishable when it arrives at the login page
|
//Add a UUID here so each config is distinguishable when it arrives at the login page.
|
||||||
for (let config of element.config.configs) {
|
element.config.configs.forEach(config => {
|
||||||
if (!config.uuid) {
|
!config.uuid && (config.uuid = uuid())
|
||||||
config.uuid = uuid()
|
})
|
||||||
}
|
|
||||||
// callback urls shouldn't be included
|
|
||||||
delete config.callbackURL
|
|
||||||
}
|
|
||||||
if (partialOidc) {
|
if (partialOidc) {
|
||||||
if (!oidcComplete) {
|
if (!oidcComplete) {
|
||||||
notifications.error(
|
notifications.error(
|
||||||
`Please fill in all required ${ConfigTypes.OIDC} fields`
|
`Please fill in all required ${ConfigTypes.OIDC} fields`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
calls.push(api.post(`/api/global/configs`, element))
|
calls.push(api.post(`/api/admin/configs`, element))
|
||||||
// turn the save button grey when clicked
|
// turn the save button grey when clicked
|
||||||
oidcSaveButtonDisabled = true
|
oidcSaveButtonDisabled = true
|
||||||
originalOidcDoc = cloneDeep(providers.oidc)
|
originalOidcDoc = cloneDeep(providers.oidc)
|
||||||
|
@ -187,8 +173,7 @@
|
||||||
`Please fill in all required ${ConfigTypes.Google} fields`
|
`Please fill in all required ${ConfigTypes.Google} fields`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
delete element.config.callbackURL
|
calls.push(api.post(`/api/admin/configs`, element))
|
||||||
calls.push(api.post(`/api/global/configs`, element))
|
|
||||||
googleSaveButtonDisabled = true
|
googleSaveButtonDisabled = true
|
||||||
originalGoogleDoc = cloneDeep(providers.google)
|
originalGoogleDoc = cloneDeep(providers.google)
|
||||||
}
|
}
|
||||||
|
@ -221,7 +206,7 @@
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
// fetch the configs for oauth
|
// fetch the configs for oauth
|
||||||
const googleResponse = await api.get(
|
const googleResponse = await api.get(
|
||||||
`/api/global/configs/${ConfigTypes.Google}`
|
`/api/admin/configs/${ConfigTypes.Google}`
|
||||||
)
|
)
|
||||||
const googleDoc = await googleResponse.json()
|
const googleDoc = await googleResponse.json()
|
||||||
|
|
||||||
|
@ -242,7 +227,7 @@
|
||||||
|
|
||||||
//Get the list of user uploaded logos and push it to the dropdown options.
|
//Get the list of user uploaded logos and push it to the dropdown options.
|
||||||
//This needs to be done before the config call so they're available when the dropdown renders
|
//This needs to be done before the config call so they're available when the dropdown renders
|
||||||
const res = await api.get(`/api/global/configs/logos_oidc`)
|
const res = await api.get(`/api/admin/configs/logos_oidc`)
|
||||||
const configSettings = await res.json()
|
const configSettings = await res.json()
|
||||||
|
|
||||||
if (configSettings.config) {
|
if (configSettings.config) {
|
||||||
|
@ -257,16 +242,17 @@
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const oidcResponse = await api.get(
|
const oidcResponse = await api.get(`/api/admin/configs/${ConfigTypes.OIDC}`)
|
||||||
`/api/global/configs/${ConfigTypes.OIDC}`
|
|
||||||
)
|
|
||||||
const oidcDoc = await oidcResponse.json()
|
const oidcDoc = await oidcResponse.json()
|
||||||
if (!oidcDoc._id) {
|
if (!oidcDoc._id) {
|
||||||
|
console.log("hi")
|
||||||
|
|
||||||
providers.oidc = {
|
providers.oidc = {
|
||||||
type: ConfigTypes.OIDC,
|
type: ConfigTypes.OIDC,
|
||||||
config: { configs: [{ activated: true }] },
|
config: { configs: [{ activated: true }] },
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.log("hello")
|
||||||
originalOidcDoc = cloneDeep(oidcDoc)
|
originalOidcDoc = cloneDeep(oidcDoc)
|
||||||
providers.oidc = oidcDoc
|
providers.oidc = oidcDoc
|
||||||
}
|
}
|
||||||
|
@ -309,12 +295,8 @@
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
{#each GoogleConfigFields.Google as field}
|
{#each GoogleConfigFields.Google as field}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L">{field.label}</Label>
|
<Label size="L">{GoogleConfigLabels.Google[field]}</Label>
|
||||||
<Input
|
<Input bind:value={providers.google.config[field]} />
|
||||||
bind:value={providers.google.config[field.name]}
|
|
||||||
readonly={field.readonly}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -353,14 +335,14 @@
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
{#each OIDCConfigFields.Oidc as field}
|
{#each OIDCConfigFields.Oidc as field}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L">{field.label}</Label>
|
<Label size="L">{OIDCConfigLabels.Oidc[field]}</Label>
|
||||||
<Input
|
<Input bind:value={providers.oidc.config.configs[0][field]} />
|
||||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
|
||||||
readonly={field.readonly}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
<div class="form-row">
|
||||||
|
<Label size="L">Callback URL</Label>
|
||||||
|
<Input readonly placeholder="/api/admin/auth/oidc/callback" />
|
||||||
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
To customize your login button, fill out the fields below.
|
To customize your login button, fill out the fields below.
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
delete smtp.config.auth
|
delete smtp.config.auth
|
||||||
}
|
}
|
||||||
// Save your SMTP config
|
// Save your SMTP config
|
||||||
const response = await api.post(`/api/global/configs`, smtp)
|
const response = await api.post(`/api/admin/configs`, smtp)
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
const error = await response.text()
|
const error = await response.text()
|
||||||
|
@ -75,9 +75,7 @@
|
||||||
async function fetchSmtp() {
|
async function fetchSmtp() {
|
||||||
loading = true
|
loading = true
|
||||||
// fetch the configs for smtp
|
// fetch the configs for smtp
|
||||||
const smtpResponse = await api.get(
|
const smtpResponse = await api.get(`/api/admin/configs/${ConfigTypes.SMTP}`)
|
||||||
`/api/global/configs/${ConfigTypes.SMTP}`
|
|
||||||
)
|
|
||||||
const smtpDoc = await smtpResponse.json()
|
const smtpDoc = await smtpResponse.json()
|
||||||
|
|
||||||
if (!smtpDoc._id) {
|
if (!smtpDoc._id) {
|
||||||
|
@ -94,13 +92,8 @@
|
||||||
requireAuth = smtpConfig.config.auth != null
|
requireAuth = smtpConfig.config.auth != null
|
||||||
// always attach the auth for the forms purpose -
|
// always attach the auth for the forms purpose -
|
||||||
// this will be removed later if required
|
// this will be removed later if required
|
||||||
if (!smtpDoc.config) {
|
smtpConfig.config.auth = {
|
||||||
smtpDoc.config = {}
|
type: "login",
|
||||||
}
|
|
||||||
if (!smtpDoc.config.auth) {
|
|
||||||
smtpConfig.config.auth = {
|
|
||||||
type: "login",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,8 @@
|
||||||
})
|
})
|
||||||
let selectedApp
|
let selectedApp
|
||||||
|
|
||||||
const userFetch = fetchData(`/api/global/users/${userId}`)
|
const userFetch = fetchData(`/api/admin/users/${userId}`)
|
||||||
const apps = fetchData(`/api/global/roles`)
|
const apps = fetchData(`/api/admin/roles`)
|
||||||
|
|
||||||
async function deleteUser() {
|
async function deleteUser() {
|
||||||
const res = await users.delete(userId)
|
const res = await users.delete(userId)
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
async function uploadLogo(file) {
|
async function uploadLogo(file) {
|
||||||
let data = new FormData()
|
let data = new FormData()
|
||||||
data.append("file", file)
|
data.append("file", file)
|
||||||
const res = await post("/api/global/configs/upload/settings/logo", data, {})
|
const res = await post("/api/admin/configs/upload/settings/logo", data, {})
|
||||||
return await res.json()
|
return await res.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { redirect } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
import { auth } from "../stores/portal"
|
$redirect("./builder")
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
auth.checkQueryString()
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
$redirect(`./builder`)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import { auth } from "stores/portal"
|
|
||||||
|
|
||||||
export function createAdminStore() {
|
export function createAdminStore() {
|
||||||
const admin = writable({
|
const { subscribe, set } = writable({})
|
||||||
loaded: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
const tenantId = get(auth).tenantId
|
const response = await api.get("/api/admin/configs/checklist")
|
||||||
const response = await api.get(
|
|
||||||
`/api/global/configs/checklist?tenantId=${tenantId}`
|
|
||||||
)
|
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
|
|
||||||
const onboardingSteps = Object.keys(json)
|
const onboardingSteps = Object.keys(json)
|
||||||
|
@ -22,49 +16,20 @@ export function createAdminStore() {
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
|
||||||
await multiTenancyEnabled()
|
set({
|
||||||
admin.update(store => {
|
checklist: json,
|
||||||
store.loaded = true
|
onboardingProgress: (stepsComplete / onboardingSteps.length) * 100,
|
||||||
store.checklist = json
|
|
||||||
store.onboardingProgress =
|
|
||||||
(stepsComplete / onboardingSteps.length) * 100
|
|
||||||
return store
|
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
admin.update(store => {
|
set({
|
||||||
store.checklist = null
|
checklist: null,
|
||||||
return store
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function multiTenancyEnabled() {
|
|
||||||
let enabled = false
|
|
||||||
try {
|
|
||||||
const response = await api.get(`/api/system/flags`)
|
|
||||||
const json = await response.json()
|
|
||||||
enabled = json.multiTenancy
|
|
||||||
} catch (err) {
|
|
||||||
// just let it stay disabled
|
|
||||||
}
|
|
||||||
admin.update(store => {
|
|
||||||
store.multiTenancy = enabled
|
|
||||||
return store
|
|
||||||
})
|
|
||||||
return enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
function unload() {
|
|
||||||
admin.update(store => {
|
|
||||||
store.loaded = false
|
|
||||||
return store
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: admin.subscribe,
|
subscribe,
|
||||||
init,
|
init,
|
||||||
unload,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,124 +1,74 @@
|
||||||
import { derived, writable, get } from "svelte/store"
|
import { derived, writable, get } from "svelte/store"
|
||||||
import api from "../../builderStore/api"
|
import api from "../../builderStore/api"
|
||||||
import { admin } from "stores/portal"
|
|
||||||
|
|
||||||
export function createAuthStore() {
|
export function createAuthStore() {
|
||||||
const auth = writable({
|
const user = writable(null)
|
||||||
user: null,
|
const store = derived(user, $user => {
|
||||||
tenantId: "default",
|
|
||||||
tenantSet: false,
|
|
||||||
loaded: false,
|
|
||||||
})
|
|
||||||
const store = derived(auth, $store => {
|
|
||||||
let initials = null
|
let initials = null
|
||||||
let isAdmin = false
|
let isAdmin = false
|
||||||
let isBuilder = false
|
let isBuilder = false
|
||||||
if ($store.user) {
|
if ($user) {
|
||||||
const user = $store.user
|
if ($user.firstName) {
|
||||||
if (user.firstName) {
|
initials = $user.firstName[0]
|
||||||
initials = user.firstName[0]
|
if ($user.lastName) {
|
||||||
if (user.lastName) {
|
initials += $user.lastName[0]
|
||||||
initials += user.lastName[0]
|
|
||||||
}
|
}
|
||||||
} else if (user.email) {
|
} else if ($user.email) {
|
||||||
initials = user.email[0]
|
initials = $user.email[0]
|
||||||
} else {
|
} else {
|
||||||
initials = "Unknown"
|
initials = "Unknown"
|
||||||
}
|
}
|
||||||
isAdmin = !!user.admin?.global
|
isAdmin = !!$user.admin?.global
|
||||||
isBuilder = !!user.builder?.global
|
isBuilder = !!$user.builder?.global
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
user: $store.user,
|
user: $user,
|
||||||
tenantId: $store.tenantId,
|
|
||||||
tenantSet: $store.tenantSet,
|
|
||||||
loaded: $store.loaded,
|
|
||||||
initials,
|
initials,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isBuilder,
|
isBuilder,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function setUser(user) {
|
|
||||||
auth.update(store => {
|
|
||||||
store.loaded = true
|
|
||||||
store.user = user
|
|
||||||
if (user) {
|
|
||||||
store.tenantId = user.tenantId || "default"
|
|
||||||
store.tenantSet = true
|
|
||||||
}
|
|
||||||
return store
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setOrganisation(tenantId) {
|
|
||||||
const prevId = get(store).tenantId
|
|
||||||
auth.update(store => {
|
|
||||||
store.tenantId = tenantId
|
|
||||||
store.tenantSet = !!tenantId
|
|
||||||
return store
|
|
||||||
})
|
|
||||||
if (prevId !== tenantId) {
|
|
||||||
// re-init admin after setting org
|
|
||||||
await admin.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
checkQueryString: async () => {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
|
||||||
if (urlParams.has("tenantId")) {
|
|
||||||
const tenantId = urlParams.get("tenantId")
|
|
||||||
await setOrganisation(tenantId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setOrg: async tenantId => {
|
|
||||||
await setOrganisation(tenantId)
|
|
||||||
},
|
|
||||||
checkAuth: async () => {
|
checkAuth: async () => {
|
||||||
const response = await api.get("/api/global/users/self")
|
const response = await api.get("/api/admin/users/self")
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
setUser(null)
|
user.set(null)
|
||||||
} else {
|
} else {
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
setUser(json)
|
user.set(json)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
login: async creds => {
|
login: async creds => {
|
||||||
const tenantId = get(store).tenantId
|
const response = await api.post(`/api/admin/auth`, creds)
|
||||||
const response = await api.post(
|
|
||||||
`/api/global/auth/${tenantId}/login`,
|
|
||||||
creds
|
|
||||||
)
|
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
setUser(json.user)
|
user.set(json.user)
|
||||||
} else {
|
} else {
|
||||||
throw "Invalid credentials"
|
throw "Invalid credentials"
|
||||||
}
|
}
|
||||||
return json
|
return json
|
||||||
},
|
},
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
const response = await api.post(`/api/global/auth/logout`)
|
const response = await api.post(`/api/admin/auth/logout`)
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw "Unable to create logout"
|
throw "Unable to create logout"
|
||||||
}
|
}
|
||||||
await response.json()
|
await response.json()
|
||||||
setUser(null)
|
user.set(null)
|
||||||
},
|
},
|
||||||
updateSelf: async fields => {
|
updateSelf: async fields => {
|
||||||
const newUser = { ...get(auth).user, ...fields }
|
const newUser = { ...get(user), ...fields }
|
||||||
const response = await api.post("/api/global/users/self", newUser)
|
const response = await api.post("/api/admin/users/self", newUser)
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
setUser(newUser)
|
user.set(newUser)
|
||||||
} else {
|
} else {
|
||||||
throw "Unable to update user details"
|
throw "Unable to update user details"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
forgotPassword: async email => {
|
forgotPassword: async email => {
|
||||||
const tenantId = get(store).tenantId
|
const response = await api.post(`/api/admin/auth/reset`, {
|
||||||
const response = await api.post(`/api/global/auth/${tenantId}/reset`, {
|
|
||||||
email,
|
email,
|
||||||
})
|
})
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
|
@ -127,21 +77,17 @@ export function createAuthStore() {
|
||||||
await response.json()
|
await response.json()
|
||||||
},
|
},
|
||||||
resetPassword: async (password, code) => {
|
resetPassword: async (password, code) => {
|
||||||
const tenantId = get(store).tenantId
|
const response = await api.post(`/api/admin/auth/reset/update`, {
|
||||||
const response = await api.post(
|
password,
|
||||||
`/api/global/auth/${tenantId}/reset/update`,
|
resetCode: code,
|
||||||
{
|
})
|
||||||
password,
|
|
||||||
resetCode: code,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw "Unable to reset password"
|
throw "Unable to reset password"
|
||||||
}
|
}
|
||||||
await response.json()
|
await response.json()
|
||||||
},
|
},
|
||||||
createUser: async user => {
|
createUser: async user => {
|
||||||
const response = await api.post(`/api/global/users`, user)
|
const response = await api.post(`/api/admin/users`, user)
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw "Unable to create user"
|
throw "Unable to create user"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ export function createEmailStore() {
|
||||||
templates: {
|
templates: {
|
||||||
fetch: async () => {
|
fetch: async () => {
|
||||||
// fetch the email template definitions
|
// fetch the email template definitions
|
||||||
const response = await api.get(`/api/global/template/definitions`)
|
const response = await api.get(`/api/admin/template/definitions`)
|
||||||
const definitions = await response.json()
|
const definitions = await response.json()
|
||||||
|
|
||||||
// fetch the email templates themselves
|
// fetch the email templates themselves
|
||||||
const templatesResponse = await api.get(`/api/global/template/email`)
|
const templatesResponse = await api.get(`/api/admin/template/email`)
|
||||||
const templates = await templatesResponse.json()
|
const templates = await templatesResponse.json()
|
||||||
|
|
||||||
store.set({
|
store.set({
|
||||||
|
@ -23,7 +23,7 @@ export function createEmailStore() {
|
||||||
},
|
},
|
||||||
save: async template => {
|
save: async template => {
|
||||||
// Save your template config
|
// Save your template config
|
||||||
const response = await api.post(`/api/global/template`, template)
|
const response = await api.post(`/api/admin/template`, template)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
if (response.status !== 200) throw new Error(json.message)
|
if (response.status !== 200) throw new Error(json.message)
|
||||||
template._rev = json._rev
|
template._rev = json._rev
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import { auth } from "stores/portal"
|
|
||||||
|
|
||||||
const OIDC_CONFIG = {
|
const OIDC_CONFIG = {
|
||||||
logo: undefined,
|
logo: undefined,
|
||||||
|
@ -13,13 +12,10 @@ export function createOidcStore() {
|
||||||
const { set, subscribe } = store
|
const { set, subscribe } = store
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const tenantId = get(auth).tenantId
|
const res = await api.get(`/api/admin/configs/publicOidc`)
|
||||||
const res = await api.get(
|
|
||||||
`/api/global/configs/public/oidc?tenantId=${tenantId}`
|
|
||||||
)
|
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
|
|
||||||
if (json.status === 400 || Object.keys(json).length === 0) {
|
if (json.status === 400) {
|
||||||
set(OIDC_CONFIG)
|
set(OIDC_CONFIG)
|
||||||
} else {
|
} else {
|
||||||
// Just use the first config for now. We will be support multiple logins buttons later on.
|
// Just use the first config for now. We will be support multiple logins buttons later on.
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import { auth } from "stores/portal"
|
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
platformUrl: "http://localhost:10000",
|
platformUrl: "http://localhost:1000",
|
||||||
logoUrl: undefined,
|
logoUrl: undefined,
|
||||||
docsUrl: undefined,
|
docsUrl: undefined,
|
||||||
company: "Budibase",
|
company: "Budibase",
|
||||||
|
@ -16,8 +15,7 @@ export function createOrganisationStore() {
|
||||||
const { subscribe, set } = store
|
const { subscribe, set } = store
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const tenantId = get(auth).tenantId
|
const res = await api.get(`/api/admin/configs/public`)
|
||||||
const res = await api.get(`/api/global/configs/public?tenantId=${tenantId}`)
|
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
|
|
||||||
if (json.status === 400) {
|
if (json.status === 400) {
|
||||||
|
@ -28,7 +26,7 @@ export function createOrganisationStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(config) {
|
async function save(config) {
|
||||||
const res = await api.post("/api/global/configs", {
|
const res = await api.post("/api/admin/configs", {
|
||||||
type: "settings",
|
type: "settings",
|
||||||
config: { ...get(store), ...config },
|
config: { ...get(store), ...config },
|
||||||
_rev: get(store)._rev,
|
_rev: get(store)._rev,
|
||||||
|
|
|
@ -6,7 +6,7 @@ export function createUsersStore() {
|
||||||
const { subscribe, set } = writable([])
|
const { subscribe, set } = writable([])
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const response = await api.get(`/api/global/users`)
|
const response = await api.get(`/api/admin/users`)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
set(json)
|
set(json)
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,12 @@ export function createUsersStore() {
|
||||||
global: true,
|
global: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const response = await api.post(`/api/global/users/invite`, body)
|
const response = await api.post(`/api/admin/users/invite`, body)
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function acceptInvite(inviteCode, password) {
|
async function acceptInvite(inviteCode, password) {
|
||||||
const response = await api.post("/api/global/users/invite/accept", {
|
const response = await api.post("/api/admin/users/invite/accept", {
|
||||||
inviteCode,
|
inviteCode,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
|
@ -47,20 +47,20 @@ export function createUsersStore() {
|
||||||
if (admin) {
|
if (admin) {
|
||||||
body.admin = { global: true }
|
body.admin = { global: true }
|
||||||
}
|
}
|
||||||
const response = await api.post("/api/global/users", body)
|
const response = await api.post("/api/admin/users", body)
|
||||||
await init()
|
await init()
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del(id) {
|
async function del(id) {
|
||||||
const response = await api.delete(`/api/global/users/${id}`)
|
const response = await api.delete(`/api/admin/users/${id}`)
|
||||||
update(users => users.filter(user => user._id !== id))
|
update(users => users.filter(user => user._id !== id))
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(data) {
|
async function save(data) {
|
||||||
try {
|
try {
|
||||||
const res = await post(`/api/global/users`, data)
|
const res = await post(`/api/admin/users`, data)
|
||||||
return await res.json()
|
return await res.json()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "0.9.87-alpha.9",
|
"version": "0.9.95",
|
||||||
"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": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "0.9.87-alpha.9",
|
"version": "0.9.95",
|
||||||
"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",
|
||||||
|
@ -18,9 +18,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.87-alpha.9",
|
"@budibase/bbui": "^0.9.95",
|
||||||
"@budibase/standard-components": "^0.9.87-alpha.9",
|
"@budibase/standard-components": "^0.9.95",
|
||||||
"@budibase/string-templates": "^0.9.87-alpha.9",
|
"@budibase/string-templates": "^0.9.95",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const logIn = async ({ email, password }) => {
|
||||||
return API.error("Please enter your password")
|
return API.error("Please enter your password")
|
||||||
}
|
}
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: "/api/global/auth",
|
url: "/api/admin/auth",
|
||||||
body: { username: email, password },
|
body: { username: email, password },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ export const logIn = async ({ email, password }) => {
|
||||||
*/
|
*/
|
||||||
export const fetchSelf = async () => {
|
export const fetchSelf = async () => {
|
||||||
const user = await API.get({ url: "/api/self" })
|
const user = await API.get({ url: "/api/self" })
|
||||||
if (user && user._id) {
|
if (user?._id) {
|
||||||
if (user.roleId === "PUBLIC") {
|
if (user.roleId === "PUBLIC") {
|
||||||
// Don't try to enrich a public user as it will 403
|
// Don't try to enrich a public user as it will 403
|
||||||
return user
|
return user
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
{:else if $screenStore.activeLayout}
|
{:else if $screenStore.activeLayout}
|
||||||
<Provider key="user" data={$authStore} {actions}>
|
<Provider key="user" data={$authStore} {actions}>
|
||||||
<div id="app-root">
|
<div id="app-root" class:preview={$builderStore.inBuilder}>
|
||||||
{#key $screenStore.activeLayout._id}
|
{#key $screenStore.activeLayout._id}
|
||||||
<Component instance={$screenStore.activeLayout.props} />
|
<Component instance={$screenStore.activeLayout.props} />
|
||||||
{/key}
|
{/key}
|
||||||
|
@ -133,6 +133,9 @@
|
||||||
#app-root {
|
#app-root {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
#app-root.preview {
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
}
|
||||||
|
|
||||||
/* Custom scrollbars */
|
/* Custom scrollbars */
|
||||||
:global(::-webkit-scrollbar) {
|
:global(::-webkit-scrollbar) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ module FetchMock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.includes("/api/global")) {
|
if (url.includes("/api/admin")) {
|
||||||
return json({
|
return json({
|
||||||
email: "test@test.com",
|
email: "test@test.com",
|
||||||
_id: "us_test@test.com",
|
_id: "us_test@test.com",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.87-alpha.9",
|
"version": "0.9.95",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -23,9 +23,7 @@
|
||||||
"format": "prettier --config ../../.prettierrc.json 'src/**/*.ts' --write",
|
"format": "prettier --config ../../.prettierrc.json 'src/**/*.ts' --write",
|
||||||
"lint": "eslint --fix src/",
|
"lint": "eslint --fix src/",
|
||||||
"lint:fix": "yarn run format && yarn run lint",
|
"lint:fix": "yarn run format && yarn run lint",
|
||||||
"initialise": "node scripts/initialise.js",
|
"initialise": "node scripts/initialise.js"
|
||||||
"multi:enable": "node scripts/multiTenancy.js enable",
|
|
||||||
"multi:disable": "node scripts/multiTenancy.js disable"
|
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "ts-jest",
|
"preset": "ts-jest",
|
||||||
|
@ -62,9 +60,9 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.87-alpha.9",
|
"@budibase/auth": "^0.9.95",
|
||||||
"@budibase/client": "^0.9.87-alpha.9",
|
"@budibase/client": "^0.9.95",
|
||||||
"@budibase/string-templates": "^0.9.87-alpha.9",
|
"@budibase/string-templates": "^0.9.95",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
"@koa/router": "8.0.0",
|
"@koa/router": "8.0.0",
|
||||||
"@sendgrid/mail": "7.1.1",
|
"@sendgrid/mail": "7.1.1",
|
||||||
|
@ -117,7 +115,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.14.3",
|
"@babel/core": "^7.14.3",
|
||||||
"@babel/preset-env": "^7.14.4",
|
"@babel/preset-env": "^7.14.4",
|
||||||
"@budibase/standard-components": "^0.9.87-alpha.9",
|
"@budibase/standard-components": "^0.9.95",
|
||||||
"@jest/test-sequencer": "^24.8.0",
|
"@jest/test-sequencer": "^24.8.0",
|
||||||
"@types/bull": "^3.15.1",
|
"@types/bull": "^3.15.1",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
|
@ -138,8 +136,7 @@
|
||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"ts-jest": "^27.0.3",
|
"ts-jest": "^27.0.3",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^4.3.4",
|
"typescript": "^4.3.4"
|
||||||
"update-dotenv": "^1.1.1"
|
|
||||||
},
|
},
|
||||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,29 +33,26 @@ async function init() {
|
||||||
fs.writeFileSync(envoyOutputPath, processStringSync(contents, config))
|
fs.writeFileSync(envoyOutputPath, processStringSync(contents, config))
|
||||||
|
|
||||||
const envFilePath = path.join(process.cwd(), ".env")
|
const envFilePath = path.join(process.cwd(), ".env")
|
||||||
if (!fs.existsSync(envFilePath)) {
|
const envFileJson = {
|
||||||
const envFileJson = {
|
PORT: 4001,
|
||||||
PORT: 4001,
|
MINIO_URL: "http://localhost:10000/",
|
||||||
MINIO_URL: "http://localhost:10000/",
|
COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/",
|
||||||
COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/",
|
REDIS_URL: "localhost:6379",
|
||||||
REDIS_URL: "localhost:6379",
|
WORKER_URL: "http://localhost:4002",
|
||||||
WORKER_URL: "http://localhost:4002",
|
INTERNAL_API_KEY: "budibase",
|
||||||
INTERNAL_API_KEY: "budibase",
|
JWT_SECRET: "testsecret",
|
||||||
JWT_SECRET: "testsecret",
|
REDIS_PASSWORD: "budibase",
|
||||||
REDIS_PASSWORD: "budibase",
|
MINIO_ACCESS_KEY: "budibase",
|
||||||
MINIO_ACCESS_KEY: "budibase",
|
MINIO_SECRET_KEY: "budibase",
|
||||||
MINIO_SECRET_KEY: "budibase",
|
COUCH_DB_PASSWORD: "budibase",
|
||||||
COUCH_DB_PASSWORD: "budibase",
|
COUCH_DB_USER: "budibase",
|
||||||
COUCH_DB_USER: "budibase",
|
SELF_HOSTED: 1,
|
||||||
SELF_HOSTED: 1,
|
|
||||||
MULTI_TENANCY: "",
|
|
||||||
}
|
|
||||||
let envFile = ""
|
|
||||||
Object.keys(envFileJson).forEach(key => {
|
|
||||||
envFile += `${key}=${envFileJson[key]}\n`
|
|
||||||
})
|
|
||||||
fs.writeFileSync(envFilePath, envFile)
|
|
||||||
}
|
}
|
||||||
|
let envFile = ""
|
||||||
|
Object.keys(envFileJson).forEach(key => {
|
||||||
|
envFile += `${key}=${envFileJson[key]}\n`
|
||||||
|
})
|
||||||
|
fs.writeFileSync(envFilePath, envFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function up() {
|
async function up() {
|
||||||
|
|
|
@ -10,11 +10,10 @@ CREATE TABLE Persons (
|
||||||
CREATE TABLE Tasks (
|
CREATE TABLE Tasks (
|
||||||
TaskID SERIAL PRIMARY KEY,
|
TaskID SERIAL PRIMARY KEY,
|
||||||
PersonID INT,
|
PersonID INT,
|
||||||
Completed BOOLEAN,
|
|
||||||
TaskName varchar(255),
|
TaskName varchar(255),
|
||||||
CONSTRAINT fkPersons
|
CONSTRAINT fkPersons
|
||||||
FOREIGN KEY(PersonID)
|
FOREIGN KEY(PersonID)
|
||||||
REFERENCES Persons(PersonID)
|
REFERENCES Persons(PersonID)
|
||||||
);
|
);
|
||||||
CREATE TABLE Products (
|
CREATE TABLE Products (
|
||||||
ProductID SERIAL PRIMARY KEY,
|
ProductID SERIAL PRIMARY KEY,
|
||||||
|
@ -25,15 +24,15 @@ CREATE TABLE Products_Tasks (
|
||||||
TaskID INT NOT NULL,
|
TaskID INT NOT NULL,
|
||||||
CONSTRAINT fkProducts
|
CONSTRAINT fkProducts
|
||||||
FOREIGN KEY(ProductID)
|
FOREIGN KEY(ProductID)
|
||||||
REFERENCES Products(ProductID),
|
REFERENCES Products(ProductID),
|
||||||
CONSTRAINT fkTasks
|
CONSTRAINT fkTasks
|
||||||
FOREIGN KEY(TaskID)
|
FOREIGN KEY(TaskID)
|
||||||
REFERENCES Tasks(TaskID),
|
REFERENCES Tasks(TaskID),
|
||||||
PRIMARY KEY (ProductID, TaskID)
|
PRIMARY KEY (ProductID, TaskID)
|
||||||
);
|
);
|
||||||
INSERT INTO Persons (FirstName, LastName, Address, City) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast');
|
INSERT INTO Persons (FirstName, LastName, Address, City) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast');
|
||||||
INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'assembling', TRUE);
|
INSERT INTO Tasks (PersonID, TaskName) VALUES (1, 'assembling');
|
||||||
INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'processing', FALSE);
|
INSERT INTO Tasks (PersonID, TaskName) VALUES (1, 'processing');
|
||||||
INSERT INTO Products (ProductName) VALUES ('Computers');
|
INSERT INTO Products (ProductName) VALUES ('Computers');
|
||||||
INSERT INTO Products (ProductName) VALUES ('Laptops');
|
INSERT INTO Products (ProductName) VALUES ('Laptops');
|
||||||
INSERT INTO Products (ProductName) VALUES ('Chairs');
|
INSERT INTO Products (ProductName) VALUES ('Chairs');
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
const updateDotEnv = require("update-dotenv")
|
|
||||||
|
|
||||||
const arg = process.argv.slice(2)[0]
|
|
||||||
|
|
||||||
updateDotEnv({
|
|
||||||
MULTI_TENANCY: arg === "enable" ? "1" : "",
|
|
||||||
}).then(() => console.log("Updated server!"))
|
|
|
@ -1,30 +1,8 @@
|
||||||
const { StaticDatabases } = require("@budibase/auth/db")
|
const builderDB = require("../../db/builder")
|
||||||
const { getGlobalDB } = require("@budibase/auth/tenancy")
|
|
||||||
|
|
||||||
const KEYS_DOC = StaticDatabases.GLOBAL.docs.apiKeys
|
|
||||||
|
|
||||||
async function getBuilderMainDoc() {
|
|
||||||
const db = getGlobalDB()
|
|
||||||
try {
|
|
||||||
return await db.get(KEYS_DOC)
|
|
||||||
} catch (err) {
|
|
||||||
// doesn't exist yet, nothing to get
|
|
||||||
return {
|
|
||||||
_id: KEYS_DOC,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setBuilderMainDoc(doc) {
|
|
||||||
// make sure to override the ID
|
|
||||||
doc._id = KEYS_DOC
|
|
||||||
const db = getGlobalDB()
|
|
||||||
return db.put(doc)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
try {
|
try {
|
||||||
const mainDoc = await getBuilderMainDoc()
|
const mainDoc = await builderDB.getBuilderMainDoc()
|
||||||
ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {}
|
ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
|
@ -37,12 +15,12 @@ exports.update = async function (ctx) {
|
||||||
const value = ctx.request.body.value
|
const value = ctx.request.body.value
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mainDoc = await getBuilderMainDoc()
|
const mainDoc = await builderDB.getBuilderMainDoc()
|
||||||
if (mainDoc.apiKeys == null) {
|
if (mainDoc.apiKeys == null) {
|
||||||
mainDoc.apiKeys = {}
|
mainDoc.apiKeys = {}
|
||||||
}
|
}
|
||||||
mainDoc.apiKeys[key] = value
|
mainDoc.apiKeys[key] = value
|
||||||
const resp = await setBuilderMainDoc(mainDoc)
|
const resp = await builderDB.setBuilderMainDoc(mainDoc)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
_id: resp.id,
|
_id: resp.id,
|
||||||
_rev: resp.rev,
|
_rev: resp.rev,
|
||||||
|
|
|
@ -25,7 +25,7 @@ const { BASE_LAYOUTS } = require("../../constants/layouts")
|
||||||
const { createHomeScreen } = require("../../constants/screens")
|
const { createHomeScreen } = require("../../constants/screens")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
const { processObject } = require("@budibase/string-templates")
|
const { processObject } = require("@budibase/string-templates")
|
||||||
const { getAllApps } = require("@budibase/auth/db")
|
const { getAllApps } = require("../../utilities")
|
||||||
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
||||||
const {
|
const {
|
||||||
getDeployedApps,
|
getDeployedApps,
|
||||||
|
@ -38,7 +38,6 @@ const {
|
||||||
backupClientLibrary,
|
backupClientLibrary,
|
||||||
revertClientLibrary,
|
revertClientLibrary,
|
||||||
} = require("../../utilities/fileSystem/clientLibrary")
|
} = require("../../utilities/fileSystem/clientLibrary")
|
||||||
const { getTenantId, isMultiTenant } = require("@budibase/auth/tenancy")
|
|
||||||
|
|
||||||
const URL_REGEX_SLASH = /\/|\\/g
|
const URL_REGEX_SLASH = /\/|\\/g
|
||||||
|
|
||||||
|
@ -94,8 +93,7 @@ async function getAppUrlIfNotInUse(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createInstance(template) {
|
async function createInstance(template) {
|
||||||
const tenantId = isMultiTenant() ? getTenantId() : null
|
const baseAppId = generateAppID()
|
||||||
const baseAppId = generateAppID(tenantId)
|
|
||||||
const appId = generateDevAppID(baseAppId)
|
const appId = generateDevAppID(baseAppId)
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
|
@ -130,7 +128,7 @@ async function createInstance(template) {
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
||||||
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
||||||
const apps = await getAllApps(CouchDB, { dev, all })
|
const apps = await getAllApps({ CouchDB, dev, all })
|
||||||
|
|
||||||
// get the locks for all the dev apps
|
// get the locks for all the dev apps
|
||||||
if (dev || all) {
|
if (dev || all) {
|
||||||
|
@ -222,12 +220,10 @@ exports.create = async function (ctx) {
|
||||||
url: url,
|
url: url,
|
||||||
template: ctx.request.body.template,
|
template: ctx.request.body.template,
|
||||||
instance: instance,
|
instance: instance,
|
||||||
tenantId: getTenantId(),
|
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
const response = await db.put(newApplication, { force: true })
|
await db.put(newApplication, { force: true })
|
||||||
newApplication._rev = response.rev
|
|
||||||
|
|
||||||
await createEmptyAppPackage(ctx, newApplication)
|
await createEmptyAppPackage(ctx, newApplication)
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
|
@ -299,7 +295,7 @@ exports.delete = async function (ctx) {
|
||||||
await deleteApp(ctx.params.appId)
|
await deleteApp(ctx.params.appId)
|
||||||
}
|
}
|
||||||
// make sure the app/role doesn't stick around after the app has been deleted
|
// make sure the app/role doesn't stick around after the app has been deleted
|
||||||
await removeAppFromUserRoles(ctx, ctx.params.appId)
|
await removeAppFromUserRoles(ctx.params.appId)
|
||||||
|
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = result
|
ctx.body = result
|
||||||
|
|
|
@ -22,7 +22,7 @@ exports.fetchSelf = async ctx => {
|
||||||
const userTable = await db.get(InternalTables.USER_METADATA)
|
const userTable = await db.get(InternalTables.USER_METADATA)
|
||||||
const metadata = await db.get(userId)
|
const metadata = await db.get(userId)
|
||||||
// specifically needs to make sure is enriched
|
// specifically needs to make sure is enriched
|
||||||
ctx.body = await outputProcessing(ctx, userTable, {
|
ctx.body = await outputProcessing(appId, userTable, {
|
||||||
...user,
|
...user,
|
||||||
...metadata,
|
...metadata,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const CouchDB = require("../../../db")
|
const PouchDB = require("../../../db")
|
||||||
const Deployment = require("./Deployment")
|
const Deployment = require("./Deployment")
|
||||||
const { Replication } = require("@budibase/auth/db")
|
const { Replication, StaticDatabases } = require("@budibase/auth/db")
|
||||||
const { DocumentTypes } = require("../../../db/utils")
|
const { DocumentTypes } = require("../../../db/utils")
|
||||||
|
|
||||||
// the max time we can wait for an invalidation to complete before considering it failed
|
// the max time we can wait for an invalidation to complete before considering it failed
|
||||||
|
@ -31,14 +31,13 @@ async function checkAllDeployments(deployments) {
|
||||||
async function storeDeploymentHistory(deployment) {
|
async function storeDeploymentHistory(deployment) {
|
||||||
const appId = deployment.getAppId()
|
const appId = deployment.getAppId()
|
||||||
const deploymentJSON = deployment.getJSON()
|
const deploymentJSON = deployment.getJSON()
|
||||||
const db = new CouchDB(appId)
|
const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
|
||||||
|
|
||||||
let deploymentDoc
|
let deploymentDoc
|
||||||
try {
|
try {
|
||||||
// theres only one deployment doc per app database
|
deploymentDoc = await db.get(appId)
|
||||||
deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
deploymentDoc = { _id: DocumentTypes.DEPLOYMENTS, history: {} }
|
deploymentDoc = { _id: appId, history: {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
const deploymentId = deploymentJSON._id
|
const deploymentId = deploymentJSON._id
|
||||||
|
@ -68,7 +67,7 @@ async function deployApp(deployment) {
|
||||||
})
|
})
|
||||||
|
|
||||||
await replication.replicate()
|
await replication.replicate()
|
||||||
const db = new CouchDB(productionAppId)
|
const db = new PouchDB(productionAppId)
|
||||||
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
||||||
appDoc.appId = productionAppId
|
appDoc.appId = productionAppId
|
||||||
appDoc.instance._id = productionAppId
|
appDoc.instance._id = productionAppId
|
||||||
|
@ -99,9 +98,8 @@ async function deployApp(deployment) {
|
||||||
|
|
||||||
exports.fetchDeployments = async function (ctx) {
|
exports.fetchDeployments = async function (ctx) {
|
||||||
try {
|
try {
|
||||||
const appId = ctx.appId
|
const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
|
||||||
const db = new CouchDB(appId)
|
const deploymentDoc = await db.get(ctx.appId)
|
||||||
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
|
||||||
const { updated, deployments } = await checkAllDeployments(
|
const { updated, deployments } = await checkAllDeployments(
|
||||||
deploymentDoc,
|
deploymentDoc,
|
||||||
ctx.user
|
ctx.user
|
||||||
|
@ -117,9 +115,8 @@ exports.fetchDeployments = async function (ctx) {
|
||||||
|
|
||||||
exports.deploymentProgress = async function (ctx) {
|
exports.deploymentProgress = async function (ctx) {
|
||||||
try {
|
try {
|
||||||
const appId = ctx.appId
|
const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
|
||||||
const db = new CouchDB(appId)
|
const deploymentDoc = await db.get(ctx.appId)
|
||||||
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
|
||||||
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(
|
ctx.throw(
|
||||||
|
|
|
@ -9,9 +9,8 @@ const { DocumentTypes } = require("../../db/utils")
|
||||||
|
|
||||||
async function redirect(ctx, method) {
|
async function redirect(ctx, method) {
|
||||||
const { devPath } = ctx.params
|
const { devPath } = ctx.params
|
||||||
const queryString = ctx.originalUrl.split("?")[1] || ""
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(`${env.WORKER_URL}/api/global/${devPath}?${queryString}`),
|
checkSlashesInUrl(`${env.WORKER_URL}/api/admin/${devPath}`),
|
||||||
request(
|
request(
|
||||||
ctx,
|
ctx,
|
||||||
{
|
{
|
||||||
|
|
|
@ -161,7 +161,7 @@ exports.fetchView = async ctx => {
|
||||||
schema: {},
|
schema: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rows = await outputProcessing(ctx, table, response.rows)
|
rows = await outputProcessing(appId, table, response.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (calculation === CALCULATION_TYPES.STATS) {
|
if (calculation === CALCULATION_TYPES.STATS) {
|
||||||
|
@ -204,7 +204,7 @@ exports.fetch = async ctx => {
|
||||||
)
|
)
|
||||||
rows = response.rows.map(row => row.doc)
|
rows = response.rows.map(row => row.doc)
|
||||||
}
|
}
|
||||||
return outputProcessing(ctx, table, rows)
|
return outputProcessing(appId, table, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async ctx => {
|
exports.find = async ctx => {
|
||||||
|
@ -212,7 +212,7 @@ exports.find = async ctx => {
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const table = await db.get(ctx.params.tableId)
|
const table = await db.get(ctx.params.tableId)
|
||||||
let row = await findRow(ctx, db, ctx.params.tableId, ctx.params.rowId)
|
let row = await findRow(ctx, db, ctx.params.tableId, ctx.params.rowId)
|
||||||
row = await outputProcessing(ctx, table, row)
|
row = await outputProcessing(appId, table, row)
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +291,7 @@ exports.search = async ctx => {
|
||||||
// Enrich search results with relationships
|
// Enrich search results with relationships
|
||||||
if (response.rows && response.rows.length) {
|
if (response.rows && response.rows.length) {
|
||||||
const table = await db.get(tableId)
|
const table = await db.get(tableId)
|
||||||
response.rows = await outputProcessing(ctx, table, response.rows)
|
response.rows = await outputProcessing(appId, table, response.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -328,7 +328,7 @@ exports.fetchEnrichedRow = async ctx => {
|
||||||
})
|
})
|
||||||
// need to include the IDs in these rows for any links they may have
|
// need to include the IDs in these rows for any links they may have
|
||||||
let linkedRows = await outputProcessing(
|
let linkedRows = await outputProcessing(
|
||||||
ctx,
|
appId,
|
||||||
table,
|
table,
|
||||||
response.rows.map(row => row.doc)
|
response.rows.map(row => row.doc)
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,7 +17,7 @@ function removeGlobalProps(user) {
|
||||||
|
|
||||||
exports.fetchMetadata = async function (ctx) {
|
exports.fetchMetadata = async function (ctx) {
|
||||||
const database = new CouchDB(ctx.appId)
|
const database = new CouchDB(ctx.appId)
|
||||||
const global = await getGlobalUsers(ctx, ctx.appId)
|
const global = await getGlobalUsers(ctx.appId)
|
||||||
const metadata = (
|
const metadata = (
|
||||||
await database.allDocs(
|
await database.allDocs(
|
||||||
getUserMetadataParams(null, {
|
getUserMetadataParams(null, {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const Router = require("@koa/router")
|
const Router = require("@koa/router")
|
||||||
const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } =
|
const { buildAuthMiddleware, auditLog } = require("@budibase/auth").auth
|
||||||
require("@budibase/auth").auth
|
|
||||||
const currentApp = require("../middleware/currentapp")
|
const currentApp = require("../middleware/currentapp")
|
||||||
const compress = require("koa-compress")
|
const compress = require("koa-compress")
|
||||||
const zlib = require("zlib")
|
const zlib = require("zlib")
|
||||||
|
@ -10,13 +9,6 @@ const env = require("../environment")
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
const NO_TENANCY_ENDPOINTS = [
|
|
||||||
{
|
|
||||||
route: "/api/analytics",
|
|
||||||
method: "GET",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
router
|
router
|
||||||
.use(
|
.use(
|
||||||
compress({
|
compress({
|
||||||
|
@ -44,8 +36,6 @@ router
|
||||||
publicAllowed: true,
|
publicAllowed: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
// nothing in the server should allow query string tenants
|
|
||||||
.use(buildTenancyMiddleware(null, NO_TENANCY_ENDPOINTS))
|
|
||||||
.use(currentApp)
|
.use(currentApp)
|
||||||
.use(auditLog)
|
.use(auditLog)
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ const { BUILDER } = require("@budibase/auth/permissions")
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router
|
router
|
||||||
.post("/api/applications", authorized(BUILDER), controller.create)
|
|
||||||
.get("/api/applications/:appId/definition", controller.fetchAppDefinition)
|
.get("/api/applications/:appId/definition", controller.fetchAppDefinition)
|
||||||
.get("/api/applications", controller.fetch)
|
.get("/api/applications", controller.fetch)
|
||||||
.get("/api/applications/:appId/appPackage", controller.fetchAppPackage)
|
.get("/api/applications/:appId/appPackage", controller.fetchAppPackage)
|
||||||
.put("/api/applications/:appId", authorized(BUILDER), controller.update)
|
.put("/api/applications/:appId", authorized(BUILDER), controller.update)
|
||||||
|
.post("/api/applications", authorized(BUILDER), controller.create)
|
||||||
.post(
|
.post(
|
||||||
"/api/applications/:appId/client/update",
|
"/api/applications/:appId/client/update",
|
||||||
authorized(BUILDER),
|
authorized(BUILDER),
|
||||||
|
|
|
@ -8,9 +8,9 @@ const router = Router()
|
||||||
|
|
||||||
if (env.isDev() || env.isTest()) {
|
if (env.isDev() || env.isTest()) {
|
||||||
router
|
router
|
||||||
.get("/api/global/:devPath(.*)", controller.redirectGet)
|
.get("/api/admin/:devPath(.*)", controller.redirectGet)
|
||||||
.post("/api/global/:devPath(.*)", controller.redirectPost)
|
.post("/api/admin/:devPath(.*)", controller.redirectPost)
|
||||||
.delete("/api/global/:devPath(.*)", controller.redirectDelete)
|
.delete("/api/admin/:devPath(.*)", controller.redirectDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
router
|
router
|
||||||
|
|
|
@ -387,7 +387,7 @@ describe("/rows", () => {
|
||||||
})
|
})
|
||||||
// the environment needs configured for this
|
// the environment needs configured for this
|
||||||
await setup.switchToSelfHosted(async () => {
|
await setup.switchToSelfHosted(async () => {
|
||||||
const enriched = await outputProcessing({ appId: config.getAppId() }, table, [row])
|
const enriched = await outputProcessing(config.getAppId(), table, [row])
|
||||||
expect(enriched[0].attachment[0].url).toBe(
|
expect(enriched[0].attachment[0].url).toBe(
|
||||||
`/prod-budi-app-assets/${config.getAppId()}/attachments/test/thing.csv`
|
`/prod-budi-app-assets/${config.getAppId()}/attachments/test/thing.csv`
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,6 @@ const appController = require("../../../controllers/application")
|
||||||
const CouchDB = require("../../../../db")
|
const CouchDB = require("../../../../db")
|
||||||
const { AppStatus } = require("../../../../db/utils")
|
const { AppStatus } = require("../../../../db/utils")
|
||||||
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
|
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
|
||||||
const { TENANT_ID } = require("../../../../tests/utilities/structures")
|
|
||||||
|
|
||||||
function Request(appId, params) {
|
function Request(appId, params) {
|
||||||
this.appId = appId
|
this.appId = appId
|
||||||
|
@ -17,8 +16,8 @@ exports.getAllTableRows = async config => {
|
||||||
return req.body
|
return req.body
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.clearAllApps = async (tenantId = TENANT_ID) => {
|
exports.clearAllApps = async () => {
|
||||||
const req = { query: { status: AppStatus.DEV }, user: { tenantId } }
|
const req = { query: { status: AppStatus.DEV } }
|
||||||
await appController.fetch(req)
|
await appController.fetch(req)
|
||||||
const apps = req.body
|
const apps = req.body
|
||||||
if (!apps || apps.length <= 0) {
|
if (!apps || apps.length <= 0) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ module.exports.definition = {
|
||||||
properties: {
|
properties: {
|
||||||
text: {
|
text: {
|
||||||
type: "string",
|
type: "string",
|
||||||
title: "Log",
|
title: "URL",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["text"],
|
required: ["text"],
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue