chore(server): core IoC #68 - getAdminUsersListCollectionFactory

This commit is contained in:
Kristaps Fabians Geikins
2024-10-16 12:49:54 +03:00
parent b2efcdd951
commit bc1b3f5325
4 changed files with 185 additions and 216 deletions
@@ -175,3 +175,28 @@ export type AdminUserList = (args: AdminUserListArgs) => Promise<{
items: UserWithRole[]
cursor: string | null
}>
export type LegacyAdminUsersPaginationParams = {
limit: number
offset: number
query: string | null
}
export type LegacyAdminUsersListItem = {
registeredUser: Nullable<User>
invitedUser: Nullable<{
id: string
email: string
invitedById: string
}>
id: string
}
type LegacyAdminUsersListCollection = {
totalCount: number
items: Array<LegacyAdminUsersListItem>
}
export type LegacyGetAdminUsersListCollection = (
params: LegacyAdminUsersPaginationParams
) => Promise<LegacyAdminUsersListCollection>
@@ -1,10 +1,6 @@
const { ActionTypes } = require('@/modules/activitystream/helpers/types')
const { validateScopes } = require(`@/modules/shared`)
const zxcvbn = require('zxcvbn')
const {
getAdminUsersListCollection,
getTotalCounts
} = require('@/modules/core/services/users/adminUsersListService')
const { Roles, Scopes } = require('@speckle/shared')
const {
legacyGetUserFactory,
@@ -16,7 +12,9 @@ const {
getUserRoleFactory,
updateUserServerRoleFactory,
searchUsersFactory,
markOnboardingCompleteFactory
markOnboardingCompleteFactory,
legacyGetPaginatedUsersCountFactory,
legacyGetPaginatedUsersFactory
} = require('@/modules/core/repositories/users')
const { UsersMeta } = require('@/modules/core/dbSchema')
const { getServerInfo } = require('@/modules/core/services/generic')
@@ -42,6 +40,9 @@ const {
getUserDeletableStreamsFactory
} = require('@/modules/core/repositories/streams')
const { dbLogger } = require('@/logging/logging')
const {
getAdminUsersListCollectionFactory
} = require('@/modules/core/services/users/legacyAdminUsersList')
const getUser = legacyGetUserFactory({ db })
const getUserByEmail = legacyGetUserByEmailFactory({ db })
@@ -70,6 +71,12 @@ const changeUserRole = changeUserRoleFactory({
})
const searchUsers = searchUsersFactory({ db })
const markOnboardingComplete = markOnboardingCompleteFactory({ db })
const getAdminUsersListCollection = getAdminUsersListCollectionFactory({
countUsers: legacyGetPaginatedUsersCountFactory({ db }),
countServerInvites: countServerInvitesFactory({ db }),
findServerInvites: findServerInvitesFactory({ db }),
getUsers: legacyGetPaginatedUsersFactory({ db })
})
/** @type {import('@/modules/core/graph/generated/graphql').Resolvers} */
module.exports = {
@@ -109,12 +116,7 @@ module.exports = {
},
async adminUsers(_parent, args) {
return await getAdminUsersListCollection({
findServerInvites: findServerInvitesFactory({ db }),
getTotalCounts: getTotalCounts({
countServerInvites: countServerInvitesFactory({ db })
})
})(args)
return await getAdminUsersListCollection(args)
},
async userSearch(parent, args, context) {
@@ -1,205 +0,0 @@
const { db } = require('@/db/knex')
const {
legacyGetPaginatedUsersCountFactory,
legacyGetPaginatedUsersFactory
} = require('@/modules/core/repositories/users')
const { resolveTarget } = require('@/modules/serverinvites/helpers/core')
const { clamp } = require('lodash')
/**
* @typedef {{
* id: string,
* email: string,
* invitedById: string
* }} ServerInviteGraphqlReturnType
*/
/**
* @typedef {{
* registeredUser: import("@/modules/core/helpers/userHelper").UserRecord | null,
* invitedUser: ServerInviteGraphqlReturnType | null,
* id: string
* }} AdminUsersListItem
*/
/**
* @typedef {{
* totalCount: number,
* items: Array<AdminUsersListItem>
* }} AdminUsersListCollection
*/
/**
* @typedef {{
* limit: number,
* offset: number,
* query: string | null
* }} PaginationParams
*/
/**
* @typedef {{
* userCount: number,
* inviteCount: number,
* totalCount: number
* }} TotalCounts
*/
/**
* @typedef {{
* invitesFilter: {offset: number, limit: number} | null,
* usersFilter: {offset: number, limit: number} | null
* }} UsersInvitesFilters
*/
/**
* Sanitizing params to ensure limits aren't too high etc.
* @param {PaginationParams} params
* @returns {PaginationParams}
*/
function sanitizeParams(params) {
params.limit = clamp(params.limit || 10, 1, 200)
params.offset = Math.max(params.offset || 0, 0)
}
/**
* Get total users & invites that we can find using these params
* @param {{ countServerInvites: import('@/modules/serverinvites/domain/operations').CountServerInvites}} param0
*/
function getTotalCounts({ countServerInvites }) {
const countUsers = legacyGetPaginatedUsersCountFactory({ db })
/**
* Get total users & invites that we can find using these params
* @param {PaginationParams} params
* @returns {Promise<TotalCounts>}
*/
return async (params) => {
const { query } = params
const [userCount, inviteCount] = await Promise.all([
// Actual users
countUsers(query),
// Invites
countServerInvites(query)
])
const totalCount = userCount + inviteCount
return { userCount, inviteCount, totalCount }
}
}
/**
* Resolve limits & offsets for user & invite queries. All invites will always appear first,
* and only once there are no more results will users start appearing in the list
* @param {PaginationParams} params
* @param {TotalCounts} totalCounts
* @returns {UsersInvitesFilters}
*/
function resolveLimitsAndOffsets(params, totalCounts) {
const { offset, limit } = params
const { inviteCount } = totalCounts
const inviteOffset = offset < inviteCount ? offset : null
const inviteLimit =
inviteOffset !== null ? Math.min(inviteCount - offset, limit) : null
const userOffset = inviteOffset !== null ? 0 : offset - inviteCount
const userLimit = limit - (inviteLimit || 0)
return {
invitesFilter: inviteLimit ? { limit: inviteLimit, offset: inviteOffset } : null,
usersFilter: userLimit ? { limit: userLimit, offset: userOffset } : null
}
}
/**
* @param {import('@/modules/core/helpers/userHelper').UserRecord} user
* @returns {AdminUsersListItem}
*/
function mapUserToListItem(user) {
return {
invitedUser: null,
registeredUser: user,
id: `user:${user.id}`
}
}
/**
* @param {import('@/modules/serverinvites/domain/types').ServerInviteRecord} invite
* @returns {AdminUsersListItem}
*/
function mapInviteToListItem(invite) {
return {
registeredUser: null,
invitedUser: {
id: invite.id,
invitedById: invite.inviterId,
email: resolveTarget(invite.target).userEmail
},
id: `invite:${invite.id}`
}
}
/**
*
* @param {{findServerInvites: import('@/modules/serverinvites/domain/operations').FindServerInvites}} param0
*/
function retrieveItems({ findServerInvites }) {
const getUsers = legacyGetPaginatedUsersFactory({ db })
/**
* Retrieve all list items from DB and convert them to the target model
* @param {PaginationParams} params
* @param {TotalCounts} counts
* @returns {Promise<AdminUsersListItem[]>}
*/
return async (params, counts) => {
const { invitesFilter, usersFilter } = resolveLimitsAndOffsets(params, counts)
const { query } = params
const [invites, users] = await Promise.all([
// Invites
invitesFilter
? findServerInvites(query, invitesFilter.limit, invitesFilter.offset)
: [],
// Users
usersFilter ? getUsers(usersFilter.limit, usersFilter.offset, query) : []
])
return [
// Invites first
...invites.map((i) => mapInviteToListItem(i)),
// Users after
...users.map((u) => mapUserToListItem(u))
]
}
}
/**
*
* @param {{ getTotalCounts: (params: PaginationParams) => Promise<number>, findServerInvites: import('@/modules/serverinvites/domain/operations').FindServerInvites}} param0
*/
function getAdminUsersListCollection({ getTotalCounts, findServerInvites }) {
/**
* Resolve admin users list data using the specified filter params
* @param {PaginationParams} params
* @returns {Promise<AdminUsersListCollection>}
*/
return async (params) => {
sanitizeParams(params)
const totalCounts = await getTotalCounts(params)
const items = await retrieveItems({ findServerInvites })(params, totalCounts)
return {
items,
totalCount: totalCounts.totalCount
}
}
}
module.exports = {
getAdminUsersListCollection,
getTotalCounts
}
@@ -0,0 +1,147 @@
import {
LegacyAdminUsersListItem,
LegacyAdminUsersPaginationParams,
LegacyGetAdminUsersListCollection,
LegacyGetPaginatedUsers,
LegacyGetPaginatedUsersCount
} from '@/modules/core/domain/users/operations'
import { UserRecord } from '@/modules/core/helpers/userHelper'
import {
CountServerInvites,
FindServerInvites
} from '@/modules/serverinvites/domain/operations'
import { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
import { resolveTarget } from '@/modules/serverinvites/helpers/core'
import { clamp } from 'lodash'
type LegacyGetUsersInvitesTotalCounts = {
userCount: number
inviteCount: number
totalCount: number
}
type GetTotalCountsDeps = {
countUsers: LegacyGetPaginatedUsersCount
countServerInvites: CountServerInvites
}
/**
* Get total users & invites that we can find using these params
*/
const getTotalCountsFactory =
(deps: GetTotalCountsDeps) =>
async (
params: LegacyAdminUsersPaginationParams
): Promise<LegacyGetUsersInvitesTotalCounts> => {
const { query } = params
const [userCount, inviteCount] = await Promise.all([
// Actual users
deps.countUsers(query),
// Invites
deps.countServerInvites(query)
])
const totalCount = userCount + inviteCount
return { userCount, inviteCount, totalCount }
}
/**
* Sanitizing params to ensure limits aren't too high etc.
*/
function sanitizeParams(params: LegacyAdminUsersPaginationParams) {
params.limit = clamp(params.limit || 10, 1, 200)
params.offset = Math.max(params.offset || 0, 0)
}
/**
* Resolve limits & offsets for user & invite queries. All invites will always appear first,
* and only once there are no more results will users start appearing in the list
*/
function resolveLimitsAndOffsets(
params: LegacyAdminUsersPaginationParams,
totalCounts: LegacyGetUsersInvitesTotalCounts
) {
const { offset, limit } = params
const { inviteCount } = totalCounts
const inviteOffset = offset < inviteCount ? offset : null
const inviteLimit =
inviteOffset !== null ? Math.min(inviteCount - offset, limit) : null
const userOffset = inviteOffset !== null ? 0 : offset - inviteCount
const userLimit = limit - (inviteLimit || 0)
return {
invitesFilter: inviteLimit ? { limit: inviteLimit, offset: inviteOffset } : null,
usersFilter: userLimit ? { limit: userLimit, offset: userOffset } : null
}
}
function mapUserToListItem(user: UserRecord): LegacyAdminUsersListItem {
return {
invitedUser: null,
registeredUser: user,
id: `user:${user.id}`
}
}
function mapInviteToListItem(invite: ServerInviteRecord): LegacyAdminUsersListItem {
return {
registeredUser: null,
invitedUser: {
id: invite.id,
invitedById: invite.inviterId,
email: resolveTarget(invite.target).userEmail!
},
id: `invite:${invite.id}`
}
}
type RetrieveItemsDeps = {
findServerInvites: FindServerInvites
getUsers: LegacyGetPaginatedUsers
}
const retrieveItemsFactory =
(deps: RetrieveItemsDeps) =>
async (
params: LegacyAdminUsersPaginationParams,
counts: LegacyGetUsersInvitesTotalCounts
) => {
const { invitesFilter, usersFilter } = resolveLimitsAndOffsets(params, counts)
const { query } = params
const [invites, users] = await Promise.all([
// Invites
invitesFilter
? deps.findServerInvites(query, invitesFilter.limit, invitesFilter.offset || 0)
: [],
// Users
usersFilter ? deps.getUsers(usersFilter.limit, usersFilter.offset, query) : []
])
return [
// Invites first
...invites.map((i) => mapInviteToListItem(i)),
// Users after
...users.map((u) => mapUserToListItem(u))
]
}
/**
* Resolve admin users list data using the specified filter params
*/
export const getAdminUsersListCollectionFactory =
(deps: GetTotalCountsDeps & RetrieveItemsDeps): LegacyGetAdminUsersListCollection =>
async (params) => {
sanitizeParams(params)
const totalCounts = await getTotalCountsFactory(deps)(params)
const items = await retrieveItemsFactory(deps)(params, totalCounts)
return {
items,
totalCount: totalCounts.totalCount
}
}