Merge pull request #1039 from Budibase/feature/current-user-datasource

Add current user relationships as a data source, and current user bindings
This commit is contained in:
Andrew Kingston 2021-01-29 16:21:12 +00:00 committed by GitHub
commit ea1ca745d1
10 changed files with 98 additions and 16 deletions

View File

@ -2,6 +2,7 @@ import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store"
import { backendUiStore, store } from "builderStore"
import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
import { TableNames } from "../constants"
// Regex to match all instances of template strings
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
}

View File

@ -1,4 +1,6 @@
import API from "./api"
import { enrichRows } from "./rows"
import { TableNames } from "../constants"
/**
* Performs a log in request.
@ -15,3 +17,15 @@ export const logIn = async ({ 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 NotificationDisplay from "./NotificationDisplay.svelte"
import SDK from "../sdk"
import {
createDataStore,
initialise,
screenStore,
notificationStore,
} from "../store"
import { createDataStore, initialise, screenStore, authStore } from "../store"
// Provide contexts
setContext("sdk", SDK)
@ -22,6 +17,7 @@
// Load app config
onMount(async () => {
await initialise()
await authStore.actions.fetchUser()
loaded = true
})
</script>

View File

@ -4,7 +4,7 @@
import * as ComponentLibrary from "@budibase/standard-components"
import Router from "./Router.svelte"
import { enrichProps, propsAreSame } from "../utils/componentProps"
import { bindingStore, builderStore } from "../store"
import { authStore, bindingStore, builderStore } from "../store"
export let definition = {}
@ -23,7 +23,7 @@
$: constructor = getComponentConstructor(definition._component)
$: children = definition._children || []
$: id = definition._id
$: enrichComponentProps(definition, $dataContext, $bindingStore)
$: enrichComponentProps(definition, $dataContext, $bindingStore, $authStore)
$: updateProps(enrichedProps)
$: styles = definition._styles
@ -67,8 +67,8 @@
}
// Enriches any string component props using handlebars
const enrichComponentProps = async (definition, context, bindingStore) => {
enrichedProps = await enrichProps(definition, context, bindingStore)
const enrichComponentProps = async (definition, context, bindings, user) => {
enrichedProps = await enrichProps(definition, context, bindings, user)
}
// 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 { routeStore } from "./routes"
import { builderStore } from "./builder"
import { TableNames } from "../constants"
const createAuthStore = () => {
const store = writable("")
const store = writable(null)
const goToDefaultRoute = () => {
// 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
routeStore.actions.navigate("/")
}
// Logs a user in
const logIn = async ({ email, password }) => {
const user = await API.logIn({ email, password })
if (!user.error) {
store.set(user.token)
store.set(user)
await initialise()
goToDefaultRoute()
}
}
// Logs a user out
const logOut = async () => {
store.set("")
store.set(null)
const appId = get(builderStore).appId
if (appId) {
for (let environment of ["local", "cloud"]) {
@ -35,9 +40,26 @@ const createAuthStore = () => {
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 {
const user = await API.fetchSelf()
store.set(user)
}
}
return {
subscribe: store.subscribe,
actions: { logIn, logOut },
actions: { logIn, logOut, fetchUser },
}
}

View File

@ -21,7 +21,7 @@ export const propsAreSame = (a, b) => {
* Enriches component props.
* 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
let validProps = {}
Object.entries(props)
@ -35,6 +35,7 @@ export const enrichProps = async (props, dataContexts, dataBindings) => {
const context = {
...dataContexts,
...dataBindings,
user,
data: dataContexts[dataContexts.closestComponentId],
data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`],
}

View File

@ -57,3 +57,17 @@ exports.authenticate = async ctx => {
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

@ -177,7 +177,6 @@ exports.serveApp = async function(ctx) {
})
const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8")
console.log(appHbs)
ctx.body = await processString(appHbs, {
head,
body: html,

View File

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