Create Tenant endpoint + tenant_info doc (#13902)
* Create Tenant endpoint + tenant_info doc * Don't catch on tenant_info put * PR comments * unit test
This commit is contained in:
parent
51fad26be0
commit
115737c46c
|
@ -1,6 +1,16 @@
|
||||||
import { getDB } from "../db/db"
|
import { getDB } from "../db/db"
|
||||||
import { getGlobalDBName } from "../context"
|
import { getGlobalDBName } from "../context"
|
||||||
|
import { TenantInfo } from "@budibase/types"
|
||||||
|
|
||||||
export function getTenantDB(tenantId: string) {
|
export function getTenantDB(tenantId: string) {
|
||||||
return getDB(getGlobalDBName(tenantId))
|
return getDB(getGlobalDBName(tenantId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveTenantInfo(tenantInfo: TenantInfo) {
|
||||||
|
const db = getTenantDB(tenantInfo.tenantId)
|
||||||
|
// save the tenant info to db
|
||||||
|
return await db.put({
|
||||||
|
_id: "tenant_info",
|
||||||
|
...tenantInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -7,3 +7,4 @@ export * from "./schedule"
|
||||||
export * from "./templates"
|
export * from "./templates"
|
||||||
export * from "./environmentVariables"
|
export * from "./environmentVariables"
|
||||||
export * from "./auditLogs"
|
export * from "./auditLogs"
|
||||||
|
export * from "./tenantInfo"
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export interface TenantInfo extends Document {
|
||||||
|
owner: {
|
||||||
|
email: string
|
||||||
|
password?: string
|
||||||
|
ssoId?: string
|
||||||
|
givenName?: string
|
||||||
|
familyName?: string
|
||||||
|
budibaseUserId?: string
|
||||||
|
}
|
||||||
|
tenantId: string
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { tenancy } from "@budibase/backend-core"
|
||||||
|
import { TenantInfo, Ctx } from "@budibase/types"
|
||||||
|
|
||||||
|
export const save = async (ctx: Ctx<TenantInfo>) => {
|
||||||
|
const response = await tenancy.saveTenantInfo(ctx.request.body)
|
||||||
|
ctx.body = {
|
||||||
|
_id: response.id,
|
||||||
|
_rev: response.rev,
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,6 +76,10 @@ const PUBLIC_ENDPOINTS = [
|
||||||
route: "/api/global/users/invite",
|
route: "/api/global/users/invite",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
route: "/api/global/tenant",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const NO_TENANCY_ENDPOINTS = [
|
const NO_TENANCY_ENDPOINTS = [
|
||||||
|
@ -121,6 +125,10 @@ const NO_TENANCY_ENDPOINTS = [
|
||||||
route: "/api/global/users/invite/:code",
|
route: "/api/global/users/invite/:code",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
route: "/api/global/tenant",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// most public endpoints are gets, but some are posts
|
// most public endpoints are gets, but some are posts
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import Router from "@koa/router"
|
||||||
|
import Joi from "joi"
|
||||||
|
import { auth } from "@budibase/backend-core"
|
||||||
|
import * as controller from "../../controllers/global/tenant"
|
||||||
|
import cloudRestricted from "../../../middleware/cloudRestricted"
|
||||||
|
|
||||||
|
const router: Router = new Router()
|
||||||
|
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
|
||||||
|
|
||||||
|
function buildTenantInfoValidation() {
|
||||||
|
return auth.joiValidator.body(
|
||||||
|
Joi.object({
|
||||||
|
owner: Joi.object({
|
||||||
|
email: Joi.string().required(),
|
||||||
|
password: OPTIONAL_STRING,
|
||||||
|
ssoId: OPTIONAL_STRING,
|
||||||
|
givenName: OPTIONAL_STRING,
|
||||||
|
familyName: OPTIONAL_STRING,
|
||||||
|
budibaseUserId: OPTIONAL_STRING,
|
||||||
|
}).required(),
|
||||||
|
tenantId: Joi.string().required(),
|
||||||
|
}).required()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/api/global/tenant",
|
||||||
|
cloudRestricted,
|
||||||
|
buildTenantInfoValidation(),
|
||||||
|
controller.save
|
||||||
|
)
|
||||||
|
|
||||||
|
export default router
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { TenantInfo } from "@budibase/types"
|
||||||
|
import { TestConfiguration } from "../../../../tests"
|
||||||
|
import { tenancy as _tenancy } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
const tenancy = jest.mocked(_tenancy)
|
||||||
|
|
||||||
|
describe("/api/global/tenant", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /api/global/tenant", () => {
|
||||||
|
it("should save the tenantInfo", async () => {
|
||||||
|
tenancy.saveTenantInfo = jest.fn().mockImplementation(async () => ({
|
||||||
|
id: "DOC_ID",
|
||||||
|
ok: true,
|
||||||
|
rev: "DOC_REV",
|
||||||
|
}))
|
||||||
|
const tenantInfo: TenantInfo = {
|
||||||
|
owner: {
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "PASSWORD",
|
||||||
|
ssoId: "SSO_ID",
|
||||||
|
givenName: "Jane",
|
||||||
|
familyName: "Doe",
|
||||||
|
budibaseUserId: "USER_ID",
|
||||||
|
},
|
||||||
|
tenantId: "tenant123",
|
||||||
|
}
|
||||||
|
const response = await config.api.tenants.saveTenantInfo(tenantInfo)
|
||||||
|
|
||||||
|
expect(_tenancy.saveTenantInfo).toHaveBeenCalledTimes(1)
|
||||||
|
expect(_tenancy.saveTenantInfo).toHaveBeenCalledWith(tenantInfo)
|
||||||
|
expect(response.text).toEqual('{"_id":"DOC_ID","_rev":"DOC_REV"}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,6 +1,7 @@
|
||||||
import Router from "@koa/router"
|
import Router from "@koa/router"
|
||||||
import { api as pro } from "@budibase/pro"
|
import { api as pro } from "@budibase/pro"
|
||||||
import userRoutes from "./global/users"
|
import userRoutes from "./global/users"
|
||||||
|
import tenantRoutes from "./global/tenant"
|
||||||
import configRoutes from "./global/configs"
|
import configRoutes from "./global/configs"
|
||||||
import workspaceRoutes from "./global/workspaces"
|
import workspaceRoutes from "./global/workspaces"
|
||||||
import templateRoutes from "./global/templates"
|
import templateRoutes from "./global/templates"
|
||||||
|
@ -40,6 +41,7 @@ export const routes: Router[] = [
|
||||||
accountRoutes,
|
accountRoutes,
|
||||||
restoreRoutes,
|
restoreRoutes,
|
||||||
eventRoutes,
|
eventRoutes,
|
||||||
|
tenantRoutes,
|
||||||
pro.scim,
|
pro.scim,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TenantInfo } from "@budibase/types"
|
||||||
import TestConfiguration from "../TestConfiguration"
|
import TestConfiguration from "../TestConfiguration"
|
||||||
import { TestAPI, TestAPIOpts } from "./base"
|
import { TestAPI, TestAPIOpts } from "./base"
|
||||||
|
|
||||||
|
@ -14,4 +15,12 @@ export class TenantAPI extends TestAPI {
|
||||||
.set(opts?.headers)
|
.set(opts?.headers)
|
||||||
.expect(opts?.status ? opts.status : 204)
|
.expect(opts?.status ? opts.status : 204)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveTenantInfo = (tenantInfo: TenantInfo) => {
|
||||||
|
return this.request
|
||||||
|
.post("/api/global/tenant")
|
||||||
|
.set(this.config.internalAPIHeaders())
|
||||||
|
.send(tenantInfo)
|
||||||
|
.expect(200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -3483,10 +3483,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
|
|
||||||
"@koa/cors@^3.1.0":
|
"@koa/cors@^5.0.0":
|
||||||
version "3.4.3"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.4.3.tgz#d669ee6e8d6e4f0ec4a7a7b0a17e7a3ed3752ebb"
|
resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-5.0.0.tgz#0029b5f057fa0d0ae0e37dd2c89ece315a0daffd"
|
||||||
integrity sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==
|
integrity sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==
|
||||||
dependencies:
|
dependencies:
|
||||||
vary "^1.1.2"
|
vary "^1.1.2"
|
||||||
|
|
||||||
|
@ -5797,10 +5797,10 @@
|
||||||
"@types/koa-compose" "*"
|
"@types/koa-compose" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/koa__cors@^3.1.1":
|
"@types/koa__cors@^5.0.0":
|
||||||
version "3.3.1"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.3.1.tgz#0ec7543c4c620fd23451bfdd3e21b9a6aadedccd"
|
resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-5.0.0.tgz#74567a045b599266e2cd3940cef96cedecc2ef1f"
|
||||||
integrity sha512-aFGYhTFW7651KhmZZ05VG0QZJre7QxBxDj2LF1lf6GA/wSXEfKVAJxiQQWzRV4ZoMzQIO8vJBXKsUcRuvYK9qw==
|
integrity sha512-LCk/n25Obq5qlernGOK/2LUwa/2YJb2lxHUkkvYFDOpLXlVI6tKcdfCHRBQnOY4LwH6el5WOLs6PD/a8Uzau6g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/koa" "*"
|
"@types/koa" "*"
|
||||||
|
|
||||||
|
@ -16299,10 +16299,10 @@ node-source-walk@^5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.0.0"
|
"@babel/parser" "^7.0.0"
|
||||||
|
|
||||||
nodemailer@6.7.2:
|
nodemailer@6.9.13:
|
||||||
version "6.7.2"
|
version "6.9.13"
|
||||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.2.tgz#44b2ad5f7ed71b7067f7a21c4fedabaec62b85e0"
|
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.13.tgz#5b292bf1e92645f4852ca872c56a6ba6c4a3d3d6"
|
||||||
integrity sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q==
|
integrity sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==
|
||||||
|
|
||||||
nodemailer@6.9.9:
|
nodemailer@6.9.9:
|
||||||
version "6.9.9"
|
version "6.9.9"
|
||||||
|
|
Loading…
Reference in New Issue