Merge branch 'master' of github.com:Budibase/budibase into component-sdk
This commit is contained in:
commit
a0f3b16e51
|
@ -60,7 +60,7 @@ context("Create a Table", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("deletes a table", () => {
|
it("deletes a table", () => {
|
||||||
cy.contains(".nav-item", "dog").get(".actions").invoke("show").click()
|
cy.get(".actions").first().invoke("show").click()
|
||||||
cy.get("[data-cy=delete-table]").click()
|
cy.get("[data-cy=delete-table]").click()
|
||||||
cy.contains("Delete Table").click()
|
cy.contains("Delete Table").click()
|
||||||
cy.contains("dog").should("not.exist")
|
cy.contains("dog").should("not.exist")
|
||||||
|
|
|
@ -9,9 +9,9 @@ context('Create a User', () => {
|
||||||
|
|
||||||
// https://on.cypress.io/interacting-with-elements
|
// https://on.cypress.io/interacting-with-elements
|
||||||
it('should create a user', () => {
|
it('should create a user', () => {
|
||||||
cy.createUser('bbuser', 'test', 'POWER_USER')
|
cy.createUser("bbuser", "test", "ADMIN")
|
||||||
|
|
||||||
// Check to make sure user was created!
|
// // Check to make sure user was created!
|
||||||
cy.get("input[disabled]").should('have.value', 'bbuser')
|
cy.contains("bbuser").should('be.visible')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -113,23 +113,26 @@ Cypress.Commands.add("addRow", values => {
|
||||||
|
|
||||||
Cypress.Commands.add("createUser", (username, password, accessLevel) => {
|
Cypress.Commands.add("createUser", (username, password, accessLevel) => {
|
||||||
// Create User
|
// Create User
|
||||||
cy.get(".toprightnav > .settings").click()
|
|
||||||
cy.contains("Users").click()
|
cy.contains("Users").click()
|
||||||
|
|
||||||
cy.get("[name=Name]")
|
cy.contains("Create New Row").click()
|
||||||
.first()
|
|
||||||
.type(username)
|
cy.get(".modal").within(() => {
|
||||||
cy.get("[name=Password]")
|
cy.get("input")
|
||||||
.first()
|
.first()
|
||||||
.type(password)
|
.type(password)
|
||||||
|
cy.get("input")
|
||||||
|
.eq(1)
|
||||||
|
.type(username)
|
||||||
cy.get("select")
|
cy.get("select")
|
||||||
.first()
|
.first()
|
||||||
.select(accessLevel)
|
.select(accessLevel)
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
cy.get(".inputs")
|
cy.get(".buttons")
|
||||||
.contains("Create")
|
.contains("Create Row")
|
||||||
.click()
|
.click()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("addHeadlineComponent", text => {
|
Cypress.Commands.add("addHeadlineComponent", text => {
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui"
|
import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { TableNames } from "constants"
|
||||||
import Dropzone from "components/common/Dropzone.svelte"
|
import Dropzone from "components/common/Dropzone.svelte"
|
||||||
import { capitalise } from "../../../helpers"
|
import { capitalise } from "../../../helpers"
|
||||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||||
|
|
||||||
export let meta
|
export let meta
|
||||||
|
export let creating
|
||||||
export let value = meta.type === "boolean" ? false : ""
|
export let value = meta.type === "boolean" ? false : ""
|
||||||
|
|
||||||
$: type = meta.type
|
$: type = meta.type
|
||||||
$: label = capitalise(meta.name)
|
$: label = capitalise(meta.name)
|
||||||
|
$: editingUser =
|
||||||
|
!creating && $backendUiStore.selectedTable?._id === TableNames.USERS
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === 'options'}
|
{#if type === 'options'}
|
||||||
|
@ -30,5 +35,11 @@
|
||||||
{:else if type === 'link'}
|
{:else if type === 'link'}
|
||||||
<LinkedRowSelector bind:linkedRows={value} schema={meta} />
|
<LinkedRowSelector bind:linkedRows={value} schema={meta} />
|
||||||
{:else}
|
{:else}
|
||||||
<Input thin {label} data-cy="{meta.name}-input" {type} bind:value />
|
<Input
|
||||||
|
thin
|
||||||
|
{label}
|
||||||
|
data-cy="{meta.name}-input"
|
||||||
|
{type}
|
||||||
|
bind:value
|
||||||
|
disabled={editingUser} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -7,8 +7,8 @@ export async function createUser(user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveRow(row, tableId) {
|
export async function saveRow(row, tableId) {
|
||||||
const SAVE_ROWS_URL = `/api/${tableId}/rows`
|
const SAVE_ROW_URL = `/api/${tableId}/rows`
|
||||||
const response = await api.post(SAVE_ROWS_URL, row)
|
const response = await api.post(SAVE_ROW_URL, row)
|
||||||
|
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { Input, Button, TextButton, Select, Toggle } from "@budibase/bbui"
|
import { Input, Button, TextButton, Select, Toggle } from "@budibase/bbui"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import ValuesList from "components/common/ValuesList.svelte"
|
import ValuesList from "components/common/ValuesList.svelte"
|
||||||
|
@ -30,6 +31,9 @@
|
||||||
table => table._id !== $backendUiStore.draftTable._id
|
table => table._id !== $backendUiStore.draftTable._id
|
||||||
)
|
)
|
||||||
$: required = !!field?.constraints?.presence || primaryDisplay
|
$: required = !!field?.constraints?.presence || primaryDisplay
|
||||||
|
$: uneditable =
|
||||||
|
$backendUiStore.selectedTable?._id === TableNames.USERS &&
|
||||||
|
UNEDITABLE_USER_FIELDS.includes(field.name)
|
||||||
|
|
||||||
async function saveColumn() {
|
async function saveColumn() {
|
||||||
backendUiStore.update(state => {
|
backendUiStore.update(state => {
|
||||||
|
@ -87,7 +91,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="actions" class:hidden={deletion}>
|
<div class="actions" class:hidden={deletion}>
|
||||||
<Input label="Name" thin bind:value={field.name} />
|
<Input label="Name" thin bind:value={field.name} disabled={uneditable} />
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
disabled={originalName}
|
disabled={originalName}
|
||||||
|
@ -101,7 +105,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{#if field.type !== 'link'}
|
{#if field.type !== 'link' && !uneditable}
|
||||||
<Toggle
|
<Toggle
|
||||||
checked={required}
|
checked={required}
|
||||||
on:change={onChangeRequired}
|
on:change={onChangeRequired}
|
||||||
|
@ -157,7 +161,7 @@
|
||||||
bind:value={field.fieldName} />
|
bind:value={field.fieldName} />
|
||||||
{/if}
|
{/if}
|
||||||
<footer class="create-column-options">
|
<footer class="create-column-options">
|
||||||
{#if originalName}
|
{#if !uneditable && originalName}
|
||||||
<TextButton text on:click={confirmDelete}>Delete Column</TextButton>
|
<TextButton text on:click={confirmDelete}>Delete Column</TextButton>
|
||||||
{/if}
|
{/if}
|
||||||
<Button secondary on:click={onClosed}>Cancel</Button>
|
<Button secondary on:click={onClosed}>Cancel</Button>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { TableNames } from "constants"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import RowFieldControl from "../RowFieldControl.svelte"
|
import RowFieldControl from "../RowFieldControl.svelte"
|
||||||
import * as api from "../api"
|
import * as api from "../api"
|
||||||
|
@ -21,9 +22,10 @@
|
||||||
{ ...row, tableId: table._id },
|
{ ...row, tableId: table._id },
|
||||||
table._id
|
table._id
|
||||||
)
|
)
|
||||||
|
|
||||||
if (rowResponse.errors) {
|
if (rowResponse.errors) {
|
||||||
errors = Object.keys(rowResponse.errors)
|
errors = Object.entries(rowResponse.errors)
|
||||||
.map(k => ({ dataPath: k, message: rowResponse.errors[k] }))
|
.map(([key, error]) => ({ dataPath: key, message: error }))
|
||||||
.flat()
|
.flat()
|
||||||
// Prevent modal closing if there were errors
|
// Prevent modal closing if there were errors
|
||||||
return false
|
return false
|
||||||
|
@ -38,9 +40,15 @@
|
||||||
confirmText={creating ? 'Create Row' : 'Save Row'}
|
confirmText={creating ? 'Create Row' : 'Save Row'}
|
||||||
onConfirm={saveRow}>
|
onConfirm={saveRow}>
|
||||||
<ErrorsBox {errors} />
|
<ErrorsBox {errors} />
|
||||||
|
{#if creating && table._id === TableNames.USERS}
|
||||||
|
<RowFieldControl
|
||||||
|
{creating}
|
||||||
|
meta={{ name: 'password', type: 'password' }}
|
||||||
|
bind:value={row.password} />
|
||||||
|
{/if}
|
||||||
{#each tableSchema as [key, meta]}
|
{#each tableSchema as [key, meta]}
|
||||||
<div>
|
<div>
|
||||||
<RowFieldControl {meta} bind:value={row[key]} />
|
<RowFieldControl {meta} bind:value={row[key]} {creating} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { TableNames } from "constants"
|
||||||
import ListItem from "./ListItem.svelte"
|
import ListItem from "./ListItem.svelte"
|
||||||
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
import CreateTableModal from "./modals/CreateTableModal.svelte"
|
||||||
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||||
|
@ -42,7 +43,7 @@
|
||||||
{#each $backendUiStore.tables as table, idx}
|
{#each $backendUiStore.tables as table, idx}
|
||||||
<NavItem
|
<NavItem
|
||||||
border={idx > 0}
|
border={idx > 0}
|
||||||
icon="ri-table-line"
|
icon={`ri-${table._id === TableNames.USERS ? 'user' : 'table'}-line`}
|
||||||
text={table.name}
|
text={table.name}
|
||||||
selected={selectedView === `all_${table._id}`}
|
selected={selectedView === `all_${table._id}`}
|
||||||
on:click={() => selectTable(table)}>
|
on:click={() => selectTable(table)}>
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
async function deleteTable() {
|
async function deleteTable() {
|
||||||
await backendUiStore.actions.tables.delete(table)
|
await backendUiStore.actions.tables.delete(table)
|
||||||
store.store.actions.screens.delete(templateScreens)
|
store.actions.screens.delete(templateScreens)
|
||||||
await backendUiStore.actions.tables.fetch()
|
await backendUiStore.actions.tables.fetch()
|
||||||
notifier.success("Table deleted")
|
notifier.success("Table deleted")
|
||||||
hideEditor()
|
hideEditor()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { General, Users, DangerZone, APIKeys } from "./tabs"
|
import { General, DangerZone, APIKeys } from "./tabs"
|
||||||
import { Switcher, ModalContent } from "@budibase/bbui"
|
import { Switcher, ModalContent } from "@budibase/bbui"
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
|
@ -8,11 +8,6 @@
|
||||||
key: "GENERAL",
|
key: "GENERAL",
|
||||||
component: General,
|
component: General,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Users",
|
|
||||||
key: "USERS",
|
|
||||||
component: Users,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "API Keys",
|
title: "API Keys",
|
||||||
key: "API_KEYS",
|
key: "API_KEYS",
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
<script>
|
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
import { Input, Select, Button } from "@budibase/bbui"
|
|
||||||
export let user
|
|
||||||
|
|
||||||
let editMode = false
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="inputs">
|
|
||||||
<Input
|
|
||||||
disabled
|
|
||||||
thin
|
|
||||||
bind:value={user.username}
|
|
||||||
name="Name"
|
|
||||||
placeholder="Username" />
|
|
||||||
<Select disabled={!editMode} bind:value={user.accessLevelId} thin secondary>
|
|
||||||
<option value="">Choose an option</option>
|
|
||||||
<option value="ADMIN">Admin</option>
|
|
||||||
<option value="POWER_USER">Power User</option>
|
|
||||||
</Select>
|
|
||||||
{#if editMode}
|
|
||||||
<Button
|
|
||||||
blue
|
|
||||||
on:click={() => {
|
|
||||||
dispatch('save', user)
|
|
||||||
editMode = false
|
|
||||||
}}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
{:else}
|
|
||||||
<Button secondary on:click={() => (editMode = true)}>Edit</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.inputs {
|
|
||||||
display: grid;
|
|
||||||
justify-items: stretch;
|
|
||||||
grid-gap: var(--spacing-m);
|
|
||||||
grid-template-columns: 1fr 1fr 140px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,114 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Input, Select, Button, Label } from "@budibase/bbui"
|
|
||||||
import UserRow from "../UserRow.svelte"
|
|
||||||
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
// import * as api from "../api"
|
|
||||||
|
|
||||||
let username = ""
|
|
||||||
let password = ""
|
|
||||||
let accessLevelId = "ADMIN"
|
|
||||||
|
|
||||||
$: valid = username && password && accessLevelId
|
|
||||||
$: appId = $store.appId
|
|
||||||
|
|
||||||
// Create user!
|
|
||||||
async function createUser() {
|
|
||||||
if (valid) {
|
|
||||||
const user = { name: username, username, password, accessLevelId }
|
|
||||||
const response = await api.post(`/api/users`, user)
|
|
||||||
const json = await response.json()
|
|
||||||
backendUiStore.actions.users.create(json)
|
|
||||||
fetchUsersPromise = fetchUsers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update user!
|
|
||||||
async function updateUser(event) {
|
|
||||||
let data = event.detail
|
|
||||||
delete data.password
|
|
||||||
const response = await api.put(`/api/users`, data)
|
|
||||||
const users = await response.json()
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.users = users
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
fetchUsersPromise = fetchUsers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get users
|
|
||||||
async function fetchUsers() {
|
|
||||||
const response = await api.get(`/api/users`)
|
|
||||||
const users = await response.json()
|
|
||||||
backendUiStore.update(state => {
|
|
||||||
state.users = users
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return users
|
|
||||||
}
|
|
||||||
|
|
||||||
let fetchUsersPromise = fetchUsers()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div>
|
|
||||||
<Label extraSmall grey>Create New User</Label>
|
|
||||||
<div class="inputs">
|
|
||||||
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
|
||||||
<Input
|
|
||||||
thin
|
|
||||||
type="password"
|
|
||||||
bind:value={password}
|
|
||||||
name="Password"
|
|
||||||
placeholder="Password" />
|
|
||||||
<Select secondary bind:value={accessLevelId} thin>
|
|
||||||
<option value="">Choose an option</option>
|
|
||||||
<option value="ADMIN">Admin</option>
|
|
||||||
<option value="POWER_USER">Power User</option>
|
|
||||||
</Select>
|
|
||||||
<Button on:click={createUser} primary>Create</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label extraSmall grey>Current Users</Label>
|
|
||||||
{#await fetchUsersPromise}
|
|
||||||
Loading...
|
|
||||||
{:then users}
|
|
||||||
<ul>
|
|
||||||
{#each users as user}
|
|
||||||
<li>
|
|
||||||
<UserRow {user} on:save={updateUser} />
|
|
||||||
</li>
|
|
||||||
{:else}
|
|
||||||
<li>No Users found</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{:catch err}
|
|
||||||
Something went wrong when trying to fetch users. Please refresh (CMD + R /
|
|
||||||
CTRL + R) the page and try again.
|
|
||||||
{/await}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs {
|
|
||||||
display: grid;
|
|
||||||
justify-items: stretch;
|
|
||||||
grid-gap: var(--spacing-m);
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-m);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,6 +1,5 @@
|
||||||
export { default as General } from "./General.svelte"
|
export { default as General } from "./General.svelte"
|
||||||
export { default as Integrations } from "./Integrations.svelte"
|
export { default as Integrations } from "./Integrations.svelte"
|
||||||
export { default as Permissions } from "./Permissions.svelte"
|
export { default as Permissions } from "./Permissions.svelte"
|
||||||
export { default as Users } from "./Users.svelte"
|
|
||||||
export { default as APIKeys } from "./APIKeys.svelte"
|
export { default as APIKeys } from "./APIKeys.svelte"
|
||||||
export { default as DangerZone } from "./DangerZone.svelte"
|
export { default as DangerZone } from "./DangerZone.svelte"
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
export const TableNames = {
|
||||||
|
USERS: "ta_users",
|
||||||
|
}
|
||||||
|
|
||||||
|
// fields on the user table that cannot be edited
|
||||||
|
export const UNEDITABLE_USER_FIELDS = ["username", "password", "accessLevelId"]
|
||||||
|
|
||||||
export const DEFAULT_PAGES_OBJECT = {
|
export const DEFAULT_PAGES_OBJECT = {
|
||||||
main: {
|
main: {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -68,16 +68,11 @@
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<ThemeEditor />
|
<ThemeEditor />
|
||||||
<FeedbackNavLink />
|
<FeedbackNavLink />
|
||||||
<div class="topnavitemright">
|
|
||||||
<a target="_blank" href="https://docs.budibase.com">
|
|
||||||
<i class="ri-question-line" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="topnavitemright">
|
<div class="topnavitemright">
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://github.com/Budibase/budibase/discussions">
|
href="https://github.com/Budibase/budibase/discussions">
|
||||||
<i class="ri-discuss-line" />
|
<i class="ri-question-line" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<SettingsLink />
|
<SettingsLink />
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
"lodash": "^4.17.13",
|
"lodash": "^4.17.13",
|
||||||
"mustache": "^4.0.1",
|
"mustache": "^4.0.1",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
|
"open": "^7.3.0",
|
||||||
"pino-pretty": "^4.0.0",
|
"pino-pretty": "^4.0.0",
|
||||||
"pouchdb": "^7.2.1",
|
"pouchdb": "^7.2.1",
|
||||||
"pouchdb-all-dbs": "^1.0.2",
|
"pouchdb-all-dbs": "^1.0.2",
|
||||||
|
|
|
@ -26,6 +26,7 @@ const {
|
||||||
const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages")
|
const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages")
|
||||||
const { HOME_SCREEN } = require("../../constants/screens")
|
const { HOME_SCREEN } = require("../../constants/screens")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
|
||||||
|
@ -67,6 +68,9 @@ async function createInstance(template) {
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
throw "Error loading database dump from template."
|
throw "Error loading database dump from template."
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// create the users table
|
||||||
|
await db.put(USERS_TABLE_SCHEMA)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { _id: appId }
|
return { _id: appId }
|
||||||
|
|
|
@ -6,7 +6,9 @@ const {
|
||||||
generateRowID,
|
generateRowID,
|
||||||
DocumentTypes,
|
DocumentTypes,
|
||||||
SEPARATOR,
|
SEPARATOR,
|
||||||
|
ViewNames,
|
||||||
} = require("../../db/utils")
|
} = require("../../db/utils")
|
||||||
|
const usersController = require("./user")
|
||||||
const { cloneDeep } = require("lodash")
|
const { cloneDeep } = require("lodash")
|
||||||
|
|
||||||
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
|
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
|
||||||
|
@ -118,6 +120,16 @@ exports.save = async function(ctx) {
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Creation of a new user goes to the user controller
|
||||||
|
if (!existingRow && row.tableId === ViewNames.USERS) {
|
||||||
|
try {
|
||||||
|
await usersController.create(ctx)
|
||||||
|
} catch (err) {
|
||||||
|
ctx.body = { errors: [err.message] }
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (existingRow) {
|
if (existingRow) {
|
||||||
const response = await db.put(row)
|
const response = await db.put(row)
|
||||||
row._rev = response.rev
|
row._rev = response.rev
|
||||||
|
@ -315,8 +327,8 @@ exports.fetchEnrichedRow = async function(ctx) {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
function coerceRowValues(rec, table) {
|
function coerceRowValues(record, table) {
|
||||||
const row = cloneDeep(rec)
|
const row = cloneDeep(record)
|
||||||
for (let [key, value] of Object.entries(row)) {
|
for (let [key, value] of Object.entries(row)) {
|
||||||
const field = table.schema[key]
|
const field = table.schema[key]
|
||||||
if (!field) continue
|
if (!field) continue
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const CouchDB = require("../../db")
|
const CouchDB = require("../../db")
|
||||||
const bcrypt = require("../../utilities/bcrypt")
|
const bcrypt = require("../../utilities/bcrypt")
|
||||||
const { generateUserID, getUserParams } = require("../../db/utils")
|
const { generateUserID, getUserParams, ViewNames } = require("../../db/utils")
|
||||||
const {
|
const {
|
||||||
BUILTIN_LEVEL_ID_ARRAY,
|
BUILTIN_LEVEL_ID_ARRAY,
|
||||||
} = require("../../utilities/security/accessLevels")
|
} = require("../../utilities/security/accessLevels")
|
||||||
|
@ -11,7 +11,7 @@ const {
|
||||||
exports.fetch = async function(ctx) {
|
exports.fetch = async function(ctx) {
|
||||||
const database = new CouchDB(ctx.user.appId)
|
const database = new CouchDB(ctx.user.appId)
|
||||||
const data = await database.allDocs(
|
const data = await database.allDocs(
|
||||||
getUserParams(null, {
|
getUserParams("", {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -44,6 +44,7 @@ exports.create = async function(ctx) {
|
||||||
type: "user",
|
type: "user",
|
||||||
accessLevelId,
|
accessLevelId,
|
||||||
permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER],
|
permissions: permissions || [BUILTIN_PERMISSION_NAMES.POWER],
|
||||||
|
tableId: ViewNames.USERS,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -28,7 +28,7 @@ module.exports.definition = {
|
||||||
accessLevelId: {
|
accessLevelId: {
|
||||||
type: "string",
|
type: "string",
|
||||||
title: "Access Level",
|
title: "Access Level",
|
||||||
enum: accessLevels.BUILTIN_LEVEL_IDS,
|
enum: accessLevels.BUILTIN_LEVEL_ID_ARRAY,
|
||||||
pretty: accessLevels.BUILTIN_LEVEL_NAME_ARRAY,
|
pretty: accessLevels.BUILTIN_LEVEL_NAME_ARRAY,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,42 @@
|
||||||
|
const { BUILTIN_LEVEL_IDS } = require("../utilities/security/accessLevels")
|
||||||
|
|
||||||
const AuthTypes = {
|
const AuthTypes = {
|
||||||
APP: "app",
|
APP: "app",
|
||||||
BUILDER: "builder",
|
BUILDER: "builder",
|
||||||
EXTERNAL: "external",
|
EXTERNAL: "external",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const USERS_TABLE_SCHEMA = {
|
||||||
|
_id: "ta_users",
|
||||||
|
type: "table",
|
||||||
|
views: {},
|
||||||
|
name: "Users",
|
||||||
|
schema: {
|
||||||
|
username: {
|
||||||
|
type: "string",
|
||||||
|
constraints: {
|
||||||
|
type: "string",
|
||||||
|
length: {
|
||||||
|
maximum: "",
|
||||||
|
},
|
||||||
|
presence: true,
|
||||||
|
},
|
||||||
|
fieldName: "username",
|
||||||
|
name: "username",
|
||||||
|
},
|
||||||
|
accessLevelId: {
|
||||||
|
fieldName: "accessLevelId",
|
||||||
|
name: "accessLevelId",
|
||||||
|
type: "options",
|
||||||
|
constraints: {
|
||||||
|
type: "string",
|
||||||
|
presence: false,
|
||||||
|
inclusion: Object.keys(BUILTIN_LEVEL_IDS),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primaryDisplay: "username",
|
||||||
|
}
|
||||||
|
|
||||||
exports.AuthTypes = AuthTypes
|
exports.AuthTypes = AuthTypes
|
||||||
|
exports.USERS_TABLE_SCHEMA = USERS_TABLE_SCHEMA
|
||||||
|
|
|
@ -20,6 +20,7 @@ const DocumentTypes = {
|
||||||
const ViewNames = {
|
const ViewNames = {
|
||||||
LINK: "by_link",
|
LINK: "by_link",
|
||||||
ROUTING: "screen_routes",
|
ROUTING: "screen_routes",
|
||||||
|
USERS: "ta_users",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.ViewNames = ViewNames
|
exports.ViewNames = ViewNames
|
||||||
|
@ -80,13 +81,12 @@ exports.generateTableID = () => {
|
||||||
exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => {
|
exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => {
|
||||||
if (tableId == null) {
|
if (tableId == null) {
|
||||||
return getDocParams(DocumentTypes.ROW, null, otherProps)
|
return getDocParams(DocumentTypes.ROW, null, otherProps)
|
||||||
} else {
|
|
||||||
const endOfKey =
|
|
||||||
rowId == null
|
|
||||||
? `${tableId}${SEPARATOR}`
|
|
||||||
: `${tableId}${SEPARATOR}${rowId}`
|
|
||||||
return getDocParams(DocumentTypes.ROW, endOfKey, otherProps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const endOfKey =
|
||||||
|
rowId == null ? `${tableId}${SEPARATOR}` : `${tableId}${SEPARATOR}${rowId}`
|
||||||
|
|
||||||
|
return getDocParams(DocumentTypes.ROW, endOfKey, otherProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,8 +101,12 @@ exports.generateRowID = tableId => {
|
||||||
/**
|
/**
|
||||||
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
|
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
|
||||||
*/
|
*/
|
||||||
exports.getUserParams = (username = null, otherProps = {}) => {
|
exports.getUserParams = (username = "", otherProps = {}) => {
|
||||||
return getDocParams(DocumentTypes.USER, username, otherProps)
|
return getDocParams(
|
||||||
|
DocumentTypes.ROW,
|
||||||
|
`${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${username}`,
|
||||||
|
otherProps
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,7 +115,7 @@ exports.getUserParams = (username = null, otherProps = {}) => {
|
||||||
* @returns {string} The new user ID which the user doc can be stored under.
|
* @returns {string} The new user ID which the user doc can be stored under.
|
||||||
*/
|
*/
|
||||||
exports.generateUserID = username => {
|
exports.generateUserID = username => {
|
||||||
return `${DocumentTypes.USER}${SEPARATOR}${username}`
|
return `${DocumentTypes.ROW}${SEPARATOR}${ViewNames.USERS}${SEPARATOR}${DocumentTypes.USER}${SEPARATOR}${username}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3868,6 +3868,11 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
|
||||||
is-data-descriptor "^1.0.0"
|
is-data-descriptor "^1.0.0"
|
||||||
kind-of "^6.0.2"
|
kind-of "^6.0.2"
|
||||||
|
|
||||||
|
is-docker@^2.0.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
|
||||||
|
integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==
|
||||||
|
|
||||||
is-extendable@^0.1.0, is-extendable@^0.1.1:
|
is-extendable@^0.1.0, is-extendable@^0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
|
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
|
||||||
|
@ -4027,6 +4032,13 @@ is-wsl@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
|
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
|
||||||
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
|
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
|
||||||
|
|
||||||
|
is-wsl@^2.1.1:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
|
||||||
|
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
|
||||||
|
dependencies:
|
||||||
|
is-docker "^2.0.0"
|
||||||
|
|
||||||
is-yarn-global@^0.3.0:
|
is-yarn-global@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
|
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
|
||||||
|
@ -5648,6 +5660,14 @@ only@~0.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
|
resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
|
||||||
integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=
|
integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=
|
||||||
|
|
||||||
|
open@^7.3.0:
|
||||||
|
version "7.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69"
|
||||||
|
integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==
|
||||||
|
dependencies:
|
||||||
|
is-docker "^2.0.0"
|
||||||
|
is-wsl "^2.1.1"
|
||||||
|
|
||||||
optionator@^0.8.1, optionator@^0.8.3:
|
optionator@^0.8.1, optionator@^0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
||||||
|
|
Loading…
Reference in New Issue