Merge branch 'account-api-tests' into tests/offline-license
This commit is contained in:
commit
7449fd97b7
|
@ -34,7 +34,6 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
@ -58,9 +57,12 @@ jobs:
|
||||||
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
||||||
yarn release
|
yarn release
|
||||||
|
|
||||||
- name: "Get Previous tag"
|
- name: "Get Current tag"
|
||||||
id: previoustag
|
id: currenttag
|
||||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
run: |
|
||||||
|
version=v$(./scripts/getCurrentVersion.sh)
|
||||||
|
echo 'Using tag $version'
|
||||||
|
echo "::set-output name=tag::$resversionult"
|
||||||
|
|
||||||
- name: Build/release Docker images
|
- name: Build/release Docker images
|
||||||
run: |
|
run: |
|
||||||
|
@ -69,7 +71,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
||||||
BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }}
|
BUDIBASE_RELEASE_VERSION: ${{ steps.currenttag.outputs.tag }}
|
||||||
|
|
||||||
release-helm-chart:
|
release-helm-chart:
|
||||||
needs: [release-images]
|
needs: [release-images]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.8.2-alpha.2",
|
"version": "2.8.2-alpha.5",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -20,6 +20,8 @@ export enum Header {
|
||||||
TYPE = "x-budibase-type",
|
TYPE = "x-budibase-type",
|
||||||
PREVIEW_ROLE = "x-budibase-role",
|
PREVIEW_ROLE = "x-budibase-role",
|
||||||
TENANT_ID = "x-budibase-tenant-id",
|
TENANT_ID = "x-budibase-tenant-id",
|
||||||
|
VERIFICATION_CODE = "x-budibase-verification-code",
|
||||||
|
RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code",
|
||||||
TOKEN = "x-budibase-token",
|
TOKEN = "x-budibase-token",
|
||||||
CSRF_TOKEN = "x-csrf-token",
|
CSRF_TOKEN = "x-csrf-token",
|
||||||
CORRELATION_ID = "x-budibase-correlation-id",
|
CORRELATION_ID = "x-budibase-correlation-id",
|
||||||
|
|
|
@ -62,6 +62,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getInputMode = type => {
|
||||||
|
if (type === "bigint") {
|
||||||
|
return "numeric"
|
||||||
|
}
|
||||||
|
return type === "number" ? "decimal" : "text"
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
focus = autofocus
|
focus = autofocus
|
||||||
if (focus) field.focus()
|
if (focus) field.focus()
|
||||||
|
@ -103,7 +110,7 @@
|
||||||
{type}
|
{type}
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
style={align ? `text-align: ${align};` : ""}
|
style={align ? `text-align: ${align};` : ""}
|
||||||
inputmode={type === "number" ? "decimal" : "text"}
|
inputmode={getInputMode(type)}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -135,5 +135,5 @@ export const userSelectedResourceMap = derived(userStore, $userStore => {
|
||||||
})
|
})
|
||||||
|
|
||||||
export const isOnlyUser = derived(userStore, $userStore => {
|
export const isOnlyUser = derived(userStore, $userStore => {
|
||||||
return $userStore.length === 1
|
return $userStore.length < 2
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { getSchemaForDatasource } from "../../../dataBinding"
|
||||||
const fieldTypeToComponentMap = {
|
const fieldTypeToComponentMap = {
|
||||||
string: "stringfield",
|
string: "stringfield",
|
||||||
number: "numberfield",
|
number: "numberfield",
|
||||||
|
bigint: "bigintfield",
|
||||||
options: "optionsfield",
|
options: "optionsfield",
|
||||||
array: "multifieldselect",
|
array: "multifieldselect",
|
||||||
boolean: "booleanfield",
|
boolean: "booleanfield",
|
||||||
|
|
|
@ -326,6 +326,7 @@
|
||||||
FIELDS.NUMBER,
|
FIELDS.NUMBER,
|
||||||
FIELDS.BOOLEAN,
|
FIELDS.BOOLEAN,
|
||||||
FIELDS.FORMULA,
|
FIELDS.FORMULA,
|
||||||
|
FIELDS.BIGINT,
|
||||||
]
|
]
|
||||||
// no-sql or a spreadsheet
|
// no-sql or a spreadsheet
|
||||||
if (!external || table.sql) {
|
if (!external || table.sql) {
|
||||||
|
|
|
@ -52,6 +52,7 @@ const componentMap = {
|
||||||
"field/sortable": SortableFieldSelect,
|
"field/sortable": SortableFieldSelect,
|
||||||
"field/string": FormFieldSelect,
|
"field/string": FormFieldSelect,
|
||||||
"field/number": FormFieldSelect,
|
"field/number": FormFieldSelect,
|
||||||
|
"field/bigint": FormFieldSelect,
|
||||||
"field/options": FormFieldSelect,
|
"field/options": FormFieldSelect,
|
||||||
"field/boolean": FormFieldSelect,
|
"field/boolean": FormFieldSelect,
|
||||||
"field/longform": FormFieldSelect,
|
"field/longform": FormFieldSelect,
|
||||||
|
|
|
@ -228,7 +228,7 @@
|
||||||
on:change={event => (filter.value = event.detail)}
|
on:change={event => (filter.value = event.detail)}
|
||||||
{fillWidth}
|
{fillWidth}
|
||||||
/>
|
/>
|
||||||
{:else if ["string", "longform", "number", "formula"].includes(filter.type)}
|
{:else if ["string", "longform", "number", "bigint", "formula"].includes(filter.type)}
|
||||||
<Input disabled={filter.noValue} bind:value={filter.value} />
|
<Input disabled={filter.noValue} bind:value={filter.value} />
|
||||||
{:else if filter.type === "array" || (filter.type === "options" && filter.operator === "oneOf")}
|
{:else if filter.type === "array" || (filter.type === "options" && filter.operator === "oneOf")}
|
||||||
<Multiselect
|
<Multiselect
|
||||||
|
|
|
@ -53,6 +53,10 @@ export const FIELDS = {
|
||||||
numericality: { greaterThanOrEqualTo: "", lessThanOrEqualTo: "" },
|
numericality: { greaterThanOrEqualTo: "", lessThanOrEqualTo: "" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
BIGINT: {
|
||||||
|
name: "BigInt",
|
||||||
|
type: "bigint",
|
||||||
|
},
|
||||||
BOOLEAN: {
|
BOOLEAN: {
|
||||||
name: "Boolean",
|
name: "Boolean",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
|
|
|
@ -15,12 +15,7 @@
|
||||||
{
|
{
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
"icon": "ClassicGridView",
|
"icon": "ClassicGridView",
|
||||||
"children": [
|
"children": ["container", "section", "grid", "sidepanel"]
|
||||||
"container",
|
|
||||||
"section",
|
|
||||||
"grid",
|
|
||||||
"sidepanel"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Data",
|
"name": "Data",
|
||||||
|
@ -63,6 +58,7 @@
|
||||||
"fieldgroup",
|
"fieldgroup",
|
||||||
"stringfield",
|
"stringfield",
|
||||||
"numberfield",
|
"numberfield",
|
||||||
|
"bigintfield",
|
||||||
"passwordfield",
|
"passwordfield",
|
||||||
"optionsfield",
|
"optionsfield",
|
||||||
"booleanfield",
|
"booleanfield",
|
||||||
|
@ -79,13 +75,6 @@
|
||||||
{
|
{
|
||||||
"name": "Chart",
|
"name": "Chart",
|
||||||
"icon": "GraphBarVertical",
|
"icon": "GraphBarVertical",
|
||||||
"children": [
|
"children": ["bar", "line", "area", "candlestick", "pie", "donut"]
|
||||||
"bar",
|
|
||||||
"line",
|
|
||||||
"area",
|
|
||||||
"candlestick",
|
|
||||||
"pie",
|
|
||||||
"donut"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -2509,6 +2509,57 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"bigintfield": {
|
||||||
|
"name": "BigInt Field",
|
||||||
|
"icon": "TagBold",
|
||||||
|
"styles": ["size"],
|
||||||
|
"requiredAncestors": ["form"],
|
||||||
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 50
|
||||||
|
},
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field/bigint",
|
||||||
|
"label": "Field",
|
||||||
|
"key": "field",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Label",
|
||||||
|
"key": "label"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Placeholder",
|
||||||
|
"key": "placeholder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Default value",
|
||||||
|
"key": "defaultValue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Disabled",
|
||||||
|
"key": "disabled",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"passwordfield": {
|
"passwordfield": {
|
||||||
"name": "Password Field",
|
"name": "Password Field",
|
||||||
"icon": "LockClosed",
|
"icon": "LockClosed",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
const FieldTypeToComponentMap = {
|
const FieldTypeToComponentMap = {
|
||||||
string: "stringfield",
|
string: "stringfield",
|
||||||
number: "numberfield",
|
number: "numberfield",
|
||||||
|
bigint: "bigintfield",
|
||||||
options: "optionsfield",
|
options: "optionsfield",
|
||||||
array: "multifieldselect",
|
array: "multifieldselect",
|
||||||
boolean: "booleanfield",
|
boolean: "booleanfield",
|
||||||
|
|
|
@ -133,7 +133,7 @@
|
||||||
on:change={e => onOperatorChange(filter, e.detail)}
|
on:change={e => onOperatorChange(filter, e.detail)}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
/>
|
/>
|
||||||
{#if ["string", "longform", "number", "formula"].includes(filter.type)}
|
{#if ["string", "longform", "number", "bigint", "formula"].includes(filter.type)}
|
||||||
<Input disabled={filter.noValue} bind:value={filter.value} />
|
<Input disabled={filter.noValue} bind:value={filter.value} />
|
||||||
{:else if ["options", "array"].includes(filter.type)}
|
{:else if ["options", "array"].includes(filter.type)}
|
||||||
<Combobox
|
<Combobox
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
import StringField from "./StringField.svelte"
|
||||||
|
|
||||||
|
export let defaultValue
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<StringField {...$$props} type="bigint" {defaultValue} />
|
|
@ -33,7 +33,10 @@
|
||||||
formStep
|
formStep
|
||||||
)
|
)
|
||||||
|
|
||||||
$: schemaType = fieldSchema?.type !== "formula" ? fieldSchema?.type : "string"
|
$: schemaType =
|
||||||
|
fieldSchema?.type !== "formula" && fieldSchema?.type !== "bigint"
|
||||||
|
? fieldSchema?.type
|
||||||
|
: "string"
|
||||||
|
|
||||||
// Focus label when editing
|
// Focus label when editing
|
||||||
let labelNode
|
let labelNode
|
||||||
|
|
|
@ -2,6 +2,7 @@ export { default as form } from "./Form.svelte"
|
||||||
export { default as fieldgroup } from "./FieldGroup.svelte"
|
export { default as fieldgroup } from "./FieldGroup.svelte"
|
||||||
export { default as stringfield } from "./StringField.svelte"
|
export { default as stringfield } from "./StringField.svelte"
|
||||||
export { default as numberfield } from "./NumberField.svelte"
|
export { default as numberfield } from "./NumberField.svelte"
|
||||||
|
export { default as bigintfield } from "./BigIntField.svelte"
|
||||||
export { default as optionsfield } from "./OptionsField.svelte"
|
export { default as optionsfield } from "./OptionsField.svelte"
|
||||||
export { default as multifieldselect } from "./MultiFieldSelect.svelte"
|
export { default as multifieldselect } from "./MultiFieldSelect.svelte"
|
||||||
export { default as booleanfield } from "./BooleanField.svelte"
|
export { default as booleanfield } from "./BooleanField.svelte"
|
||||||
|
|
|
@ -6,6 +6,7 @@ const schemaComponentMap = {
|
||||||
string: "stringfield",
|
string: "stringfield",
|
||||||
options: "optionsfield",
|
options: "optionsfield",
|
||||||
number: "numberfield",
|
number: "numberfield",
|
||||||
|
bigint: "bigintfield",
|
||||||
datetime: "datetimefield",
|
datetime: "datetimefield",
|
||||||
boolean: "booleanfield",
|
boolean: "booleanfield",
|
||||||
formula: "stringfield",
|
formula: "stringfield",
|
||||||
|
|
|
@ -37,8 +37,12 @@
|
||||||
$: sortedBy = column.name === $sort.column
|
$: sortedBy = column.name === $sort.column
|
||||||
$: canMoveLeft = orderable && idx > 0
|
$: canMoveLeft = orderable && idx > 0
|
||||||
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
|
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
|
||||||
$: ascendingLabel = column.schema?.type === "number" ? "low-high" : "A-Z"
|
$: ascendingLabel = ["number", "bigint"].includes(column.schema?.type)
|
||||||
$: descendingLabel = column.schema?.type === "number" ? "high-low" : "Z-A"
|
? "low-high"
|
||||||
|
: "A-Z"
|
||||||
|
$: descendingLabel = ["number", "bigint"].includes(column.schema?.type)
|
||||||
|
? "high-low"
|
||||||
|
: "Z-A"
|
||||||
|
|
||||||
const editColumn = () => {
|
const editColumn = () => {
|
||||||
dispatch("edit-column", column.schema)
|
dispatch("edit-column", column.schema)
|
||||||
|
|
|
@ -18,6 +18,7 @@ const TypeIconMap = {
|
||||||
link: "DataCorrelated",
|
link: "DataCorrelated",
|
||||||
formula: "Calculator",
|
formula: "Calculator",
|
||||||
json: "Brackets",
|
json: "Brackets",
|
||||||
|
bigint: "TagBold",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getColumnIcon = column => {
|
export const getColumnIcon = column => {
|
||||||
|
|
|
@ -155,7 +155,7 @@ export default class DataFetch {
|
||||||
let sortType = "string"
|
let sortType = "string"
|
||||||
if (sortColumn) {
|
if (sortColumn) {
|
||||||
const type = schema?.[sortColumn]?.type
|
const type = schema?.[sortColumn]?.type
|
||||||
sortType = type === "number" ? "number" : "string"
|
sortType = type === "number" || type === "bigint" ? "number" : "string"
|
||||||
}
|
}
|
||||||
this.options.sortType = sortType
|
this.options.sortType = sortType
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,9 @@ function generateSchema(
|
||||||
schema.float(key)
|
schema.float(key)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case FieldTypes.BIGINT:
|
||||||
|
schema.bigint(key)
|
||||||
|
break
|
||||||
case FieldTypes.BOOLEAN:
|
case FieldTypes.BOOLEAN:
|
||||||
schema.boolean(key)
|
schema.boolean(key)
|
||||||
break
|
break
|
||||||
|
|
|
@ -48,7 +48,6 @@ const SQL_STRING_TYPE_MAP = {
|
||||||
blob: FieldTypes.STRING,
|
blob: FieldTypes.STRING,
|
||||||
long: FieldTypes.STRING,
|
long: FieldTypes.STRING,
|
||||||
text: FieldTypes.STRING,
|
text: FieldTypes.STRING,
|
||||||
bigint: FieldTypes.STRING,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SQL_BOOLEAN_TYPE_MAP = {
|
const SQL_BOOLEAN_TYPE_MAP = {
|
||||||
|
@ -59,6 +58,7 @@ const SQL_BOOLEAN_TYPE_MAP = {
|
||||||
|
|
||||||
const SQL_MISC_TYPE_MAP = {
|
const SQL_MISC_TYPE_MAP = {
|
||||||
json: FieldTypes.JSON,
|
json: FieldTypes.JSON,
|
||||||
|
bigint: FieldTypes.BIGINT,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SQL_TYPE_MAP = {
|
const SQL_TYPE_MAP = {
|
||||||
|
|
|
@ -38,7 +38,7 @@ export const getValidOperatorsForType = (
|
||||||
}[] = []
|
}[] = []
|
||||||
if (type === "string") {
|
if (type === "string") {
|
||||||
ops = stringOps
|
ops = stringOps
|
||||||
} else if (type === "number") {
|
} else if (type === "number" || type === "bigint") {
|
||||||
ops = numOps
|
ops = numOps
|
||||||
} else if (type === "options") {
|
} else if (type === "options") {
|
||||||
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
|
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Account } from "../../documents"
|
||||||
import { Hosting } from "../../sdk"
|
import { Hosting } from "../../sdk"
|
||||||
|
|
||||||
export interface CreateAccountRequest {
|
export interface CreateAccountRequest {
|
||||||
|
@ -11,3 +12,11 @@ export interface CreateAccountRequest {
|
||||||
name?: string
|
name?: string
|
||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SearchAccountsRequest {
|
||||||
|
// one or the other - not both
|
||||||
|
email?: string
|
||||||
|
tenantId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SearchAccountsResponse = Account[]
|
|
@ -15,6 +15,7 @@ export enum FieldType {
|
||||||
JSON = "json",
|
JSON = "json",
|
||||||
INTERNAL = "internal",
|
INTERNAL = "internal",
|
||||||
BARCODEQR = "barcodeqr",
|
BARCODEQR = "barcodeqr",
|
||||||
|
BIGINT = "bigint",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RowAttachment {
|
export interface RowAttachment {
|
||||||
|
|
|
@ -53,6 +53,8 @@ async function passportCallback(
|
||||||
}
|
}
|
||||||
|
|
||||||
export const login = async (ctx: Ctx<LoginRequest>, next: any) => {
|
export const login = async (ctx: Ctx<LoginRequest>, next: any) => {
|
||||||
|
const tenantId = context.getTenantId()
|
||||||
|
console.log(tenantId)
|
||||||
const email = ctx.request.body.username
|
const email = ctx.request.body.username
|
||||||
|
|
||||||
const user = await userSdk.getUserByEmail(email)
|
const user = await userSdk.getUserByEmail(email)
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import AccountInternalAPIClient from "./AccountInternalAPIClient"
|
import AccountInternalAPIClient from "./AccountInternalAPIClient"
|
||||||
import { AccountAPI, LicenseAPI } from "./apis"
|
import { AccountAPI, LicenseAPI, AuthAPI } from "./apis"
|
||||||
import { State } from "../../types"
|
import { State } from "../../types"
|
||||||
|
|
||||||
export default class AccountInternalAPI {
|
export default class AccountInternalAPI {
|
||||||
client: AccountInternalAPIClient
|
client: AccountInternalAPIClient
|
||||||
|
|
||||||
|
auth: AuthAPI
|
||||||
accounts: AccountAPI
|
accounts: AccountAPI
|
||||||
licenses: LicenseAPI
|
licenses: LicenseAPI
|
||||||
|
|
||||||
constructor(state: State) {
|
constructor(state: State) {
|
||||||
this.client = new AccountInternalAPIClient(state)
|
this.client = new AccountInternalAPIClient(state)
|
||||||
|
this.auth = new AuthAPI(this.client)
|
||||||
this.accounts = new AccountAPI(this.client)
|
this.accounts = new AccountAPI(this.client)
|
||||||
this.licenses = new LicenseAPI(this.client)
|
this.licenses = new LicenseAPI(this.client)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Response } from "node-fetch"
|
import { Response } from "node-fetch"
|
||||||
import { Account, CreateAccountRequest } from "@budibase/types"
|
import { Account, CreateAccountRequest, SearchAccountsRequest, SearchAccountsResponse } from "@budibase/types"
|
||||||
import AccountInternalAPIClient from "../AccountInternalAPIClient"
|
import AccountInternalAPIClient from "../AccountInternalAPIClient"
|
||||||
import { APIRequestOpts } from "../../../types"
|
import { APIRequestOpts } from "../../../types"
|
||||||
|
|
||||||
|
@ -72,4 +72,66 @@ export default class AccountAPI {
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteCurrentAccount() {
|
||||||
|
const [response, json] = await this.client.del(
|
||||||
|
`/api/accounts`
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyAccount(
|
||||||
|
verificationCode: string,
|
||||||
|
opts: APIRequestOpts = { doExpect: true }
|
||||||
|
): Promise<Response> {
|
||||||
|
const [response, json] = await this.client.post(
|
||||||
|
`/api/accounts/verify`,
|
||||||
|
{
|
||||||
|
body: { verificationCode },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (opts.doExpect) {
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyAccountSendEmail(
|
||||||
|
email: string,
|
||||||
|
opts: APIRequestOpts = { doExpect: true }
|
||||||
|
): Promise<Response> {
|
||||||
|
const [response, json] = await this.client.post(
|
||||||
|
`/api/accounts/verify/send`,
|
||||||
|
{
|
||||||
|
body: { email },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (opts.doExpect) {
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(
|
||||||
|
searchType: string,
|
||||||
|
search: 'email' | 'tenantId',
|
||||||
|
opts: APIRequestOpts = { doExpect: true }
|
||||||
|
): Promise<[Response, SearchAccountsResponse]> {
|
||||||
|
let body: SearchAccountsRequest = {}
|
||||||
|
|
||||||
|
if (search === 'email') {
|
||||||
|
body.email = searchType;
|
||||||
|
} else if (search === 'tenantId') {
|
||||||
|
body.tenantId = searchType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [response, json] = await this.client.post(
|
||||||
|
`/api/accounts/search`,
|
||||||
|
{body: body}
|
||||||
|
)
|
||||||
|
if (opts.doExpect) {
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
}
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
import AccountInternalAPIClient from "../AccountInternalAPIClient"
|
||||||
|
import { APIRequestOpts } from "../../../types"
|
||||||
|
|
||||||
|
export default class AuthAPI {
|
||||||
|
client: AccountInternalAPIClient
|
||||||
|
|
||||||
|
constructor(client: AccountInternalAPIClient) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(
|
||||||
|
email: string,
|
||||||
|
password: string,
|
||||||
|
opts: APIRequestOpts = { doExpect: true }
|
||||||
|
): Promise<[Response, string]> {
|
||||||
|
const [response, json] = await this.client.post(
|
||||||
|
`/api/auth/login`,
|
||||||
|
{
|
||||||
|
body: {
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (opts.doExpect) {
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
}
|
||||||
|
const cookie = response.headers.get("set-cookie")
|
||||||
|
return [response, cookie!]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
|
export { default as AuthAPI } from "./AuthAPI"
|
||||||
export { default as AccountAPI } from "./AccountAPI"
|
export { default as AccountAPI } from "./AccountAPI"
|
||||||
export { default as LicenseAPI } from "./LicenseAPI"
|
export { default as LicenseAPI } from "./LicenseAPI"
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { AccountInternalAPI } from "../api"
|
||||||
|
import { BudibaseTestConfiguration } from "../../shared"
|
||||||
|
|
||||||
|
export default class TestConfiguration<T> extends BudibaseTestConfiguration {
|
||||||
|
// apis
|
||||||
|
api: AccountInternalAPI
|
||||||
|
|
||||||
|
context: T
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.api = new AccountInternalAPI(this.state)
|
||||||
|
this.context = <T>{}
|
||||||
|
}
|
||||||
|
|
||||||
|
async beforeAll() {
|
||||||
|
await super.beforeAll()
|
||||||
|
await this.setApiKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
async afterAll() {
|
||||||
|
await super.afterAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
async setApiKey() {
|
||||||
|
const apiKeyResponse = await this.internalApi.self.getApiKey()
|
||||||
|
this.state.apiKey = apiKeyResponse.apiKey
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { generator } from "../../shared"
|
||||||
|
import { Hosting, CreateAccountRequest } from "@budibase/types"
|
||||||
|
|
||||||
|
export const generateAccount = (): CreateAccountRequest => {
|
||||||
|
const uuid = generator.guid()
|
||||||
|
|
||||||
|
const email = `${uuid}@budibase.com`
|
||||||
|
const tenant = `tenant${uuid.replace(/-/g, "")}`
|
||||||
|
|
||||||
|
return {
|
||||||
|
email,
|
||||||
|
hosting: Hosting.CLOUD,
|
||||||
|
name: email,
|
||||||
|
password: uuid,
|
||||||
|
profession: "software_engineer",
|
||||||
|
size: "10+",
|
||||||
|
tenantId: tenant,
|
||||||
|
tenantName: tenant,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * as accounts from "./accounts"
|
|
@ -0,0 +1,20 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import * as fixtures from "../../fixtures"
|
||||||
|
|
||||||
|
describe("Account API - Create Account", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Creates a new account", async () => {
|
||||||
|
await config.api.accounts.create({
|
||||||
|
...fixtures.accounts.generateAccount()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,40 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import * as fixtures from "../../fixtures"
|
||||||
|
|
||||||
|
describe("Account API - Delete Account", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Deletes an account", async () => {
|
||||||
|
await config.doInNewState(async () => {
|
||||||
|
// Create account
|
||||||
|
const createAccountRequest = fixtures.accounts.generateAccount()
|
||||||
|
await config.api.accounts.create(createAccountRequest)
|
||||||
|
|
||||||
|
// Login - Get cookie
|
||||||
|
await config.login(
|
||||||
|
createAccountRequest.email,
|
||||||
|
createAccountRequest.password,
|
||||||
|
createAccountRequest.tenantId
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete account
|
||||||
|
const res = await config.api.accounts.deleteCurrentAccount()
|
||||||
|
expect(res.status).toBe(204)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Deletes an account by ID", async () => {
|
||||||
|
const [response, account] = await config.api.accounts.create({
|
||||||
|
...fixtures.accounts.generateAccount()
|
||||||
|
})
|
||||||
|
await config.api.accounts.delete(account.accountId)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,43 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import { generator } from "../../../shared"
|
||||||
|
|
||||||
|
describe("Account API - Search for Account", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
describe("POST /api/accounts/search", () => {
|
||||||
|
describe("by tenant", () => {
|
||||||
|
it("returns 200 + empty", async () => {
|
||||||
|
const tenantId = generator.string()
|
||||||
|
const [res, body] = await config.api.accounts.search(tenantId, "tenantId")
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(body.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200 + found", async () => {
|
||||||
|
const [res, body] = await config.api.accounts.search(config.state.tenantId!, "tenantId")
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(body.length).toBe(1)
|
||||||
|
expect(body[0].tenantId).toBe(config.state.tenantId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("by email", () => {
|
||||||
|
it("returns 200 + empty", async () => {
|
||||||
|
await config.api.accounts.search(generator.word(), "email")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200 + found", async () => {
|
||||||
|
await config.api.accounts.search(generator.word(), "email")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,29 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import { generator } from "../../../shared"
|
||||||
|
import * as fixtures from "../../fixtures";
|
||||||
|
|
||||||
|
describe("Account API - Validate Account", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
const tenant = generator.word({length: 6})
|
||||||
|
const email = `${tenant}@budibase.com`
|
||||||
|
|
||||||
|
|
||||||
|
it("Validates an email", async () => {
|
||||||
|
|
||||||
|
await config.api.accounts.validateEmail(email)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Validates a tenant ID", async () => {
|
||||||
|
|
||||||
|
await config.api.accounts.validateTenantId(tenant)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,38 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import { generator } from "../../../shared"
|
||||||
|
import * as fixtures from "../../fixtures";
|
||||||
|
|
||||||
|
describe("Account API - Verify Account", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it("Verify an account", async () => {
|
||||||
|
// Create account
|
||||||
|
await config.api.accounts.create({
|
||||||
|
...fixtures.accounts.generateAccount()
|
||||||
|
})
|
||||||
|
// Invite user
|
||||||
|
|
||||||
|
// Verify account via code
|
||||||
|
await config.api.accounts.verifyAccount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Send account verification email ", async () => {
|
||||||
|
// Create account
|
||||||
|
await config.api.accounts.create({
|
||||||
|
...fixtures.accounts.generateAccount()
|
||||||
|
})
|
||||||
|
// Invite user
|
||||||
|
|
||||||
|
// Verify account via email
|
||||||
|
await config.api.accounts.verifyAccountSendEmail()
|
||||||
|
})
|
||||||
|
})
|
|
@ -68,8 +68,7 @@ async function loginAsAdmin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loginAsAccount(account: CreateAccountRequest) {
|
async function loginAsAccount(account: CreateAccountRequest) {
|
||||||
const [res, cookie] = await internalApi.auth.login(
|
const [res, cookie] = await accountsApi.auth.login(
|
||||||
account.tenantId,
|
|
||||||
account.email,
|
account.email,
|
||||||
account.password,
|
account.password,
|
||||||
API_OPTS
|
API_OPTS
|
||||||
|
@ -90,6 +89,8 @@ async function setup() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
global.qa.tenantId = account.tenantId
|
global.qa.tenantId = account.tenantId
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
global.qa.email = account.email
|
||||||
|
// @ts-ignore
|
||||||
global.qa.accountId = newAccount.accountId
|
global.qa.accountId = newAccount.accountId
|
||||||
await loginAsAccount(account)
|
await loginAsAccount(account)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,6 +23,8 @@ export default class BudibaseTestConfiguration {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.state.tenantId = global.qa.tenantId
|
this.state.tenantId = global.qa.tenantId
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
this.state.email = global.qa.email
|
||||||
|
// @ts-ignore
|
||||||
this.state.cookie = global.qa.authCookie
|
this.state.cookie = global.qa.authCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,10 +42,37 @@ export default class BudibaseTestConfiguration {
|
||||||
|
|
||||||
// AUTH
|
// AUTH
|
||||||
|
|
||||||
|
async doInNewState(task: any) {
|
||||||
|
return this.doWithState(task, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async doWithState(task: any, state: State) {
|
||||||
|
const original = this.state
|
||||||
|
|
||||||
|
// override the state
|
||||||
|
this.state.apiKey = state.apiKey
|
||||||
|
this.state.appId = state.appId
|
||||||
|
this.state.cookie = state.cookie
|
||||||
|
this.state.tableId = state.tableId
|
||||||
|
this.state.tenantId = state.tenantId
|
||||||
|
this.state.email = state.email
|
||||||
|
|
||||||
|
await task()
|
||||||
|
|
||||||
|
// restore the state
|
||||||
|
this.state.apiKey = original.apiKey
|
||||||
|
this.state.appId = original.appId
|
||||||
|
this.state.cookie = original.cookie
|
||||||
|
this.state.tableId = original.tableId
|
||||||
|
this.state.tenantId = original.tenantId
|
||||||
|
this.state.email = original.email
|
||||||
|
}
|
||||||
|
|
||||||
async login(email: string, password: string, tenantId?: string) {
|
async login(email: string, password: string, tenantId?: string) {
|
||||||
if (!tenantId && this.state.tenantId) {
|
if (!tenantId && this.state.tenantId) {
|
||||||
tenantId = this.state.tenantId
|
tenantId = this.state.tenantId
|
||||||
} else {
|
}
|
||||||
|
if (!tenantId) {
|
||||||
throw new Error("Could not determine tenant id")
|
throw new Error("Could not determine tenant id")
|
||||||
}
|
}
|
||||||
const [res, cookie] = await this.internalApi.auth.login(
|
const [res, cookie] = await this.internalApi.auth.login(
|
||||||
|
|
|
@ -4,4 +4,5 @@ export interface State {
|
||||||
cookie?: string
|
cookie?: string
|
||||||
tableId?: string
|
tableId?: string
|
||||||
tenantId?: string
|
tenantId?: string
|
||||||
|
email?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
version=$(cat lerna.json \
|
||||||
|
| grep version \
|
||||||
|
| head -1 \
|
||||||
|
| awk -F: '{gsub(/"/,"",$2);gsub(/[[:space:]]*/,"",$2); print $2}' \
|
||||||
|
| sed 's/[",]//g')
|
||||||
|
echo $version
|
|
@ -1,9 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
version=$(cat lerna.json \
|
version=$(./scripts/getCurrentVersion.sh)
|
||||||
| grep version \
|
|
||||||
| head -1 \
|
|
||||||
| awk -F: '{gsub(/"/,"",$2);gsub(/[[:space:]]*/,"",$2); print $2}' \
|
|
||||||
| sed 's/[",]//g')
|
|
||||||
echo "Setting version $version"
|
echo "Setting version $version"
|
||||||
yarn lerna exec "yarn version --no-git-tag-version --new-version=$version"
|
yarn lerna exec "yarn version --no-git-tag-version --new-version=$version"
|
||||||
echo "Updating dependencies"
|
echo "Updating dependencies"
|
||||||
|
|
Loading…
Reference in New Issue