Handle missing users

This commit is contained in:
Adria Navarro 2023-09-19 12:22:25 +02:00
parent 7b4585ce68
commit 9e1ccc35ee
3 changed files with 73 additions and 26 deletions

View File

@ -55,14 +55,14 @@ describe("user cache", () => {
const results = await getUsers(userIdsToRequest, config.tenantId) const results = await getUsers(userIdsToRequest, config.tenantId)
expect(results).toHaveLength(5) expect(results.users).toHaveLength(5)
expect(results).toEqual( expect(results).toEqual({
usersToRequest.map(u => ({ users: usersToRequest.map(u => ({
...u, ...u,
budibaseAccess: true, budibaseAccess: true,
_rev: expect.any(String), _rev: expect.any(String),
})) })),
) })
expect(tenancy.getTenantDB).toBeCalledTimes(1) expect(tenancy.getTenantDB).toBeCalledTimes(1)
expect(tenancy.getTenantDB).toBeCalledWith(config.tenantId) expect(tenancy.getTenantDB).toBeCalledWith(config.tenantId)
@ -84,16 +84,16 @@ describe("user cache", () => {
await getUsers(userIdsToRequest, config.tenantId) await getUsers(userIdsToRequest, config.tenantId)
const resultsFromCache = await getUsers(userIdsToRequest, config.tenantId) const resultsFromCache = await getUsers(userIdsToRequest, config.tenantId)
expect(resultsFromCache).toHaveLength(5) expect(resultsFromCache.users).toHaveLength(5)
expect(resultsFromCache).toEqual( expect(resultsFromCache).toEqual({
expect.arrayContaining( users: expect.arrayContaining(
usersToRequest.map(u => ({ usersToRequest.map(u => ({
...u, ...u,
budibaseAccess: true, budibaseAccess: true,
_rev: expect.any(String), _rev: expect.any(String),
})) }))
) ),
) })
expect(staticDb.allDocs).toBeCalledTimes(1) expect(staticDb.allDocs).toBeCalledTimes(1)
}) })
@ -113,16 +113,16 @@ describe("user cache", () => {
const results = await getUsers(userIdsToRequest, config.tenantId) const results = await getUsers(userIdsToRequest, config.tenantId)
expect(results).toHaveLength(5) expect(results.users).toHaveLength(5)
expect(results).toEqual( expect(results).toEqual({
expect.arrayContaining( users: expect.arrayContaining(
usersToRequest.map(u => ({ usersToRequest.map(u => ({
...u, ...u,
budibaseAccess: true, budibaseAccess: true,
_rev: expect.any(String), _rev: expect.any(String),
})) }))
) ),
) })
expect(staticDb.allDocs).toBeCalledTimes(1) expect(staticDb.allDocs).toBeCalledTimes(1)
expect(staticDb.allDocs).toBeCalledWith({ expect(staticDb.allDocs).toBeCalledWith({
@ -131,5 +131,29 @@ describe("user cache", () => {
limit: 3, limit: 3,
}) })
}) })
it("requesting existing and unexisting ids will return found ones", async () => {
const usersToRequest = _.sampleSize(users, 3)
const missingIds = [generator.guid(), generator.guid()]
const userIdsToRequest = _.shuffle([
...missingIds,
...usersToRequest.map(x => x._id!),
])
const results = await getUsers(userIdsToRequest, config.tenantId)
expect(results.users).toHaveLength(3)
expect(results).toEqual({
users: expect.arrayContaining(
usersToRequest.map(u => ({
...u,
budibaseAccess: true,
_rev: expect.any(String),
}))
),
notFoundIds: expect.arrayContaining(missingIds),
})
})
}) })
}) })

View File

@ -6,6 +6,7 @@ import env from "../environment"
import * as accounts from "../accounts" import * as accounts from "../accounts"
import { UserDB } from "../users" import { UserDB } from "../users"
import { sdk } from "@budibase/shared-core" import { sdk } from "@budibase/shared-core"
import { User } from "@budibase/types"
const EXPIRY_SECONDS = 3600 const EXPIRY_SECONDS = 3600
@ -27,7 +28,10 @@ async function populateFromDB(userId: string, tenantId: string) {
return user return user
} }
async function populateUsersFromDB(userIds: string[], tenantId: string) { async function populateUsersFromDB(
userIds: string[],
tenantId: string
): Promise<{ users: User[]; notFoundIds?: string[] }> {
const db = tenancy.getTenantDB(tenantId) const db = tenancy.getTenantDB(tenantId)
const allDocsResponse = await db.allDocs<any>({ const allDocsResponse = await db.allDocs<any>({
keys: userIds, keys: userIds,
@ -35,9 +39,22 @@ async function populateUsersFromDB(userIds: string[], tenantId: string) {
limit: userIds.length, limit: userIds.length,
}) })
const users = allDocsResponse.rows.map(r => r.doc) const { users, notFoundIds } = allDocsResponse.rows.reduce(
(p, c) => {
if (c.doc) {
p.users.push(c.doc)
} else {
p.notFoundIds ??= []
p.notFoundIds.push(c.key)
}
return p
},
{
users: [],
} as { users: User[]; notFoundIds?: string[] }
)
await Promise.all( await Promise.all(
users.map(async user => { users.map(async (user: any) => {
user.budibaseAccess = true user.budibaseAccess = true
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
const account = await accounts.getAccount(user.email) const account = await accounts.getAccount(user.email)
@ -49,7 +66,7 @@ async function populateUsersFromDB(userIds: string[], tenantId: string) {
}) })
) )
return users return { users, notFoundIds: notFoundIds }
} }
/** /**
@ -110,12 +127,16 @@ export async function getUser(
* @param {*} tenantId the tenant of the users to get * @param {*} tenantId the tenant of the users to get
* @returns * @returns
*/ */
export async function getUsers(userIds: string[], tenantId?: string) { export async function getUsers(
userIds: string[],
tenantId?: string
): Promise<{ users: User[]; notFoundIds?: string[] }> {
const client = await redis.getUserClient() const client = await redis.getUserClient()
// try cache // try cache
let usersFromCache = await client.bulkGet(userIds) let usersFromCache = await client.bulkGet<User>(userIds)
const missingUsersFromCache = userIds.filter(uid => !usersFromCache[uid]) const missingUsersFromCache = userIds.filter(uid => !usersFromCache[uid])
const users = Object.values(usersFromCache) const users = Object.values(usersFromCache)
let notFoundIds
if (missingUsersFromCache.length) { if (missingUsersFromCache.length) {
tenantId ??= context.getTenantId() tenantId ??= context.getTenantId()
@ -123,12 +144,14 @@ export async function getUsers(userIds: string[], tenantId?: string) {
missingUsersFromCache, missingUsersFromCache,
tenantId tenantId
) )
for (const userToCache of usersFromDb) {
await client.store(userToCache._id, userToCache, EXPIRY_SECONDS) notFoundIds = usersFromDb.notFoundIds
for (const userToCache of usersFromDb.users) {
await client.store(userToCache._id!, userToCache, EXPIRY_SECONDS)
} }
users.push(...usersFromDb) users.push(...usersFromDb.users)
} }
return users return { users, notFoundIds: notFoundIds }
} }
export async function invalidateUser(userId: string) { export async function invalidateUser(userId: string) {

View File

@ -242,7 +242,7 @@ class RedisWrapper {
} }
} }
async bulkGet(keys: string[]) { async bulkGet<T>(keys: string[]) {
const db = this._db const db = this._db
if (keys.length === 0) { if (keys.length === 0) {
return {} return {}