Add current user bindings, and current user relationships as data sources

This commit is contained in:
Andrew Kingston 2021-01-28 14:29:35 +00:00
parent 79277f0ee0
commit 9d870dbccc
9 changed files with 106 additions and 15 deletions

View File

@ -2,6 +2,7 @@ import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store" import { get } from "svelte/store"
import { backendUiStore, store } from "builderStore" import { backendUiStore, store } from "builderStore"
import { findAllMatchingComponents, findComponentPath } from "./storeUtils" import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
import { TableNames } from "../constants"
// Regex to match all instances of template strings // Regex to match all instances of template strings
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
@ -114,6 +115,37 @@ export const getContextBindings = (rootComponent, componentId) => {
}) })
}) })
}) })
// Add logged in user bindings
const tables = get(backendUiStore).tables
const userTable = tables.find(table => table._id === TableNames.USERS)
const schema = {
...userTable.schema,
_id: { type: "string" },
_rev: { type: "string" },
}
const keys = Object.keys(schema).sort()
keys.forEach(key => {
const fieldSchema = schema[key]
// Replace certain bindings with a new property to help display components
let runtimeBoundKey = key
if (fieldSchema.type === "link") {
runtimeBoundKey = `${key}_count`
} else if (fieldSchema.type === "attachment") {
runtimeBoundKey = `${key}_first`
}
contextBindings.push({
type: "context",
runtimeBinding: `user.${runtimeBoundKey}`,
readableBinding: `Current User.${key}`,
fieldSchema,
providerId: "user",
tableId: TableNames.USERS,
field: key,
})
})
return contextBindings return contextBindings
} }

View File

@ -1,4 +1,6 @@
import API from "./api" import API from "./api"
import { enrichRows } from "./rows"
import { TableNames } from "../constants"
/** /**
* Performs a log in request. * Performs a log in request.
@ -15,3 +17,15 @@ export const logIn = async ({ email, password }) => {
body: { email, password }, body: { email, password },
}) })
} }
/**
* Fetches the currently logged in user object
*/
export const fetchSelf = async () => {
const user = await API.get({ url: "/api/self" })
if (user?._id) {
return (await enrichRows([user], TableNames.USERS))[0]
} else {
return null
}
}

View File

@ -4,12 +4,7 @@
import Component from "./Component.svelte" import Component from "./Component.svelte"
import NotificationDisplay from "./NotificationDisplay.svelte" import NotificationDisplay from "./NotificationDisplay.svelte"
import SDK from "../sdk" import SDK from "../sdk"
import { import { createDataStore, initialise, screenStore, authStore } from "../store"
createDataStore,
initialise,
screenStore,
notificationStore,
} from "../store"
// Provide contexts // Provide contexts
setContext("sdk", SDK) setContext("sdk", SDK)
@ -22,6 +17,7 @@
// Load app config // Load app config
onMount(async () => { onMount(async () => {
await initialise() await initialise()
await authStore.actions.fetchUser()
loaded = true loaded = true
}) })
</script> </script>

View File

@ -4,7 +4,7 @@
import * as ComponentLibrary from "@budibase/standard-components" import * as ComponentLibrary from "@budibase/standard-components"
import Router from "./Router.svelte" import Router from "./Router.svelte"
import { enrichProps, propsAreSame } from "../utils/componentProps" import { enrichProps, propsAreSame } from "../utils/componentProps"
import { bindingStore, builderStore } from "../store" import { authStore, bindingStore, builderStore } from "../store"
export let definition = {} export let definition = {}
@ -23,7 +23,7 @@
$: constructor = getComponentConstructor(definition._component) $: constructor = getComponentConstructor(definition._component)
$: children = definition._children || [] $: children = definition._children || []
$: id = definition._id $: id = definition._id
$: enrichComponentProps(definition, $dataContext, $bindingStore) $: enrichComponentProps(definition, $dataContext, $bindingStore, $authStore)
$: updateProps(enrichedProps) $: updateProps(enrichedProps)
$: styles = definition._styles $: styles = definition._styles
@ -67,8 +67,8 @@
} }
// Enriches any string component props using handlebars // Enriches any string component props using handlebars
const enrichComponentProps = async (definition, context, bindingStore) => { const enrichComponentProps = async (definition, context, bindings, user) => {
enrichedProps = await enrichProps(definition, context, bindingStore) enrichedProps = await enrichProps(definition, context, bindings, user)
} }
// Returns a unique key to let svelte know when to remount components. // Returns a unique key to let svelte know when to remount components.

View File

@ -0,0 +1,3 @@
export const TableNames = {
USERS: "ta_users",
}

View File

@ -3,9 +3,10 @@ import { writable, get } from "svelte/store"
import { initialise } from "./initialise" import { initialise } from "./initialise"
import { routeStore } from "./routes" import { routeStore } from "./routes"
import { builderStore } from "./builder" import { builderStore } from "./builder"
import { TableNames } from "../constants"
const createAuthStore = () => { const createAuthStore = () => {
const store = writable("") const store = writable(null)
const goToDefaultRoute = () => { const goToDefaultRoute = () => {
// Setting the active route forces an update of the active screen ID, // Setting the active route forces an update of the active screen ID,
@ -15,16 +16,20 @@ const createAuthStore = () => {
// Navigating updates the URL to reflect this route // Navigating updates the URL to reflect this route
routeStore.actions.navigate("/") routeStore.actions.navigate("/")
} }
// Logs a user in
const logIn = async ({ email, password }) => { const logIn = async ({ email, password }) => {
const user = await API.logIn({ email, password }) const user = await API.logIn({ email, password })
if (!user.error) { if (!user.error) {
store.set(user.token) store.set(user)
await initialise() await initialise()
goToDefaultRoute() goToDefaultRoute()
} }
} }
// Logs a user out
const logOut = async () => { const logOut = async () => {
store.set("") store.set(null)
const appId = get(builderStore).appId const appId = get(builderStore).appId
if (appId) { if (appId) {
for (let environment of ["local", "cloud"]) { for (let environment of ["local", "cloud"]) {
@ -35,9 +40,34 @@ const createAuthStore = () => {
goToDefaultRoute() goToDefaultRoute()
} }
// Fetches the user object if someone is logged in and has reloaded the page
const fetchUser = async () => {
// Fetch the first user if inside the builder
if (get(builderStore).inBuilder) {
const users = await API.fetchTableData(TableNames.USERS)
if (!users.error && users[0] != null) {
store.set(users[0])
}
}
// Or fetch the current user from localstorage in a real app
else {
if (get(store) == null) {
const user = await API.fetchSelf()
if (user) {
store.set(user)
} else {
await logOut()
}
} else {
await logOut()
}
}
}
return { return {
subscribe: store.subscribe, subscribe: store.subscribe,
actions: { logIn, logOut }, actions: { logIn, logOut, fetchUser },
} }
} }

View File

@ -21,7 +21,7 @@ export const propsAreSame = (a, b) => {
* Enriches component props. * Enriches component props.
* Data bindings are enriched, and button actions are enriched. * Data bindings are enriched, and button actions are enriched.
*/ */
export const enrichProps = async (props, dataContexts, dataBindings) => { export const enrichProps = async (props, dataContexts, dataBindings, user) => {
// Exclude all private props that start with an underscore // Exclude all private props that start with an underscore
let validProps = {} let validProps = {}
Object.entries(props) Object.entries(props)
@ -35,6 +35,7 @@ export const enrichProps = async (props, dataContexts, dataBindings) => {
const context = { const context = {
...dataContexts, ...dataContexts,
...dataBindings, ...dataBindings,
user,
data: dataContexts[dataContexts.closestComponentId], data: dataContexts[dataContexts.closestComponentId],
data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`], data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`],
} }

View File

@ -57,3 +57,17 @@ exports.authenticate = async ctx => {
ctx.throw(401, "Invalid credentials.") ctx.throw(401, "Invalid credentials.")
} }
} }
exports.fetchSelf = async ctx => {
const { userId, appId } = ctx.user
if (!userId || !appId) {
ctx.body = {}
} else {
const database = new CouchDB(appId)
const user = await database.get(userId)
if (user) {
delete user.password
}
ctx.body = user
}
}

View File

@ -4,5 +4,6 @@ const controller = require("../controllers/auth")
const router = Router() const router = Router()
router.post("/api/authenticate", controller.authenticate) router.post("/api/authenticate", controller.authenticate)
router.get("/api/self", controller.fetchSelf)
module.exports = router module.exports = router