Merge pull request #3280 from specklesystems/fabians/core-ioc-57

chore(server): core IoC #57 - getUserByEmailFactory
This commit is contained in:
Alessandro Magionami
2024-10-15 15:41:41 +02:00
committed by GitHub
10 changed files with 57 additions and 35 deletions
@@ -24,6 +24,14 @@ export type GetUser = (
params?: GetUserParams
) => Promise<Nullable<UserWithOptionalRole>>
export type GetUserByEmail = (
email: string,
options?: Partial<{
skipClean: boolean
withRole: boolean
}>
) => Promise<UserWithOptionalRole | null>
export type StoreUser = (params: {
user: Omit<NullableKeysToOptional<User>, 'suuid' | 'createdAt'>
}) => Promise<User>
@@ -13,6 +13,7 @@ import { UserWithOptionalRole } from '@/modules/core/domain/users/types'
import {
CountAdminUsers,
GetUser,
GetUserByEmail,
GetUserParams,
GetUsers,
LegacyGetPaginatedUsers,
@@ -154,31 +155,35 @@ export const getUserFactory =
/**
* Get user by e-mail address
*/
export async function getUserByEmail(
email: string,
options?: Partial<{ skipClean: boolean; withRole: boolean }>
) {
const q = Users.knex<UserWithOptionalRole[]>()
.leftJoin(UserEmails.name, UserEmails.col.userId, Users.col.id)
.where({
[UserEmails.col.primary]: true
})
.whereRaw('lower("user_emails"."email") = lower(?)', [email])
const columns: (Knex.Raw<UserRecord> | string)[] = [
...Object.values(omit(Users.col, ['email', 'verified'])),
knex.raw(`(array_agg("user_emails"."email"))[1] as email`),
knex.raw(`(array_agg("user_emails"."verified"))[1] as verified`)
]
if (options?.withRole) {
// Getting first role from grouped results
columns.push(knex.raw(`(array_agg("server_acl"."role"))[1] as role`))
q.leftJoin(ServerAcl.name, ServerAcl.col.userId, Users.col.id)
export const getUserByEmailFactory =
(deps: { db: Knex }): GetUserByEmail =>
async (
email: string,
options?: Partial<{ skipClean: boolean; withRole: boolean }>
) => {
const q = tables
.users(deps.db)
.leftJoin(UserEmails.name, UserEmails.col.userId, Users.col.id)
.where({
[UserEmails.col.primary]: true
})
.whereRaw('lower("user_emails"."email") = lower(?)', [email])
const columns: (Knex.Raw<UserRecord> | string)[] = [
...Object.values(omit(Users.col, ['email', 'verified'])),
knex.raw(`(array_agg("user_emails"."email"))[1] as email`),
knex.raw(`(array_agg("user_emails"."verified"))[1] as verified`)
]
if (options?.withRole) {
// Getting first role from grouped results
columns.push(knex.raw(`(array_agg("server_acl"."role"))[1] as role`))
q.leftJoin(ServerAcl.name, ServerAcl.col.userId, Users.col.id)
}
q.columns(columns)
q.groupBy(Users.col.id)
const user = (await q.first()) as UserWithOptionalRole
return user ? (!options?.skipClean ? sanitizeUserRecord(user) : user) : null
}
q.columns(columns)
q.groupBy(Users.col.id)
const user = await q.first()
return user ? (!options?.skipClean ? sanitizeUserRecord(user) : user) : null
}
/**
* Mark a user as verified by e-mail address, and return true on success
@@ -16,7 +16,6 @@ const Users = () => UsersSchema.knex()
const Acl = () => ServerAclSchema.knex()
const { LIMITED_USER_FIELDS } = require('@/modules/core/helpers/userHelper')
const { getUserByEmail } = require('@/modules/core/repositories/users')
const { omit } = require('lodash')
const { dbLogger } = require('@/logging/logging')
const {
@@ -26,6 +25,7 @@ const {
const { Roles } = require('@speckle/shared')
const { db } = require('@/db/knex')
const { deleteStreamFactory } = require('@/modules/core/repositories/streams')
const { getUserByEmailFactory } = require('@/modules/core/repositories/users')
const _changeUserRole = async ({ userId, role }) =>
await Acl().where({ userId }).update({ role })
@@ -42,6 +42,7 @@ const _ensureAtleastOneAdminRemains = async (userId) => {
}
}
}
const getUserByEmail = getUserByEmailFactory({ db })
module.exports = {
// TODO: this should be moved to repository
@@ -13,7 +13,7 @@ import { db } from '@/db/knex'
import { expect } from 'chai'
import {
countAdminUsersFactory,
getUserByEmail,
getUserByEmailFactory,
getUserFactory,
getUsersFactory,
listUsers,
@@ -62,6 +62,7 @@ const createUser = createUserFactory({
}),
usersEventsEmitter: UsersEmitter.emit
})
const getUserByEmail = getUserByEmailFactory({ db })
describe('Find users @core', () => {
describe('getUsers', () => {
@@ -3,7 +3,7 @@ import { beforeEachContext } from '@/test/hooks'
import { expect } from 'chai'
import {
countAdminUsersFactory,
getUserByEmail,
getUserByEmailFactory,
getUserFactory,
legacyGetPaginatedUsersCount,
legacyGetPaginatedUsersFactory,
@@ -81,6 +81,7 @@ const createUser = createUserFactory({
validateAndCreateUserEmail: createUserEmail,
usersEventsEmitter: UsersEmitter.emit
})
const getUserByEmail = getUserByEmailFactory({ db })
describe('Core @user-emails', () => {
before(async () => {
@@ -1,7 +1,10 @@
import { db } from '@/db/knex'
import { Resolvers } from '@/modules/core/graph/generated/graphql'
import { findPrimaryEmailForUserFactory } from '@/modules/core/repositories/userEmails'
import { getUserByEmail, getUserFactory } from '@/modules/core/repositories/users'
import {
getUserByEmailFactory,
getUserFactory
} from '@/modules/core/repositories/users'
import { getServerInfo } from '@/modules/core/services/generic'
import {
deleteOldAndInsertNewVerificationFactory,
@@ -20,6 +23,7 @@ const requestEmailVerification = requestEmailVerificationFactory({
sendEmail,
renderEmail
})
const getUserByEmail = getUserByEmailFactory({ db })
export = {
User: {
@@ -1,6 +1,6 @@
import { db } from '@/db/knex'
import { deleteExistingAuthTokensFactory } from '@/modules/auth/repositories'
import { getUserByEmail } from '@/modules/core/repositories/users'
import { getUserByEmailFactory } from '@/modules/core/repositories/users'
import { getServerInfo } from '@/modules/core/services/generic'
import { updateUserPassword } from '@/modules/core/services/users'
import { renderEmail } from '@/modules/emails/services/emailRendering'
@@ -16,6 +16,8 @@ import { ensureError } from '@/modules/shared/helpers/errorHelper'
import { Express } from 'express'
export default function (app: Express) {
const getUserByEmail = getUserByEmailFactory({ db })
// sends a password recovery email.
app.post('/auth/pwdreset/request', async (req, res) => {
try {
@@ -1,11 +1,11 @@
import { DeleteExistingUserAuthTokens } from '@/modules/auth/domain/operations'
import { getUserByEmail } from '@/modules/core/repositories/users'
import { GetUserByEmail } from '@/modules/core/domain/users/operations'
import { updateUserPassword } from '@/modules/core/services/users'
import { DeleteTokens, GetPendingToken } from '@/modules/pwdreset/domain/operations'
import { PasswordRecoveryFinalizationError } from '@/modules/pwdreset/errors'
type InitializeStateDeps = {
getUserByEmail: typeof getUserByEmail
getUserByEmail: GetUserByEmail
getPendingToken: GetPendingToken
}
@@ -1,5 +1,5 @@
import { GetUserByEmail } from '@/modules/core/domain/users/operations'
import { getPasswordResetFinalizationRoute } from '@/modules/core/helpers/routeHelper'
import { getUserByEmail } from '@/modules/core/repositories/users'
import { getServerInfo } from '@/modules/core/services/generic'
import {
EmailTemplateParams,
@@ -14,7 +14,7 @@ import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
const EMAIL_SUBJECT = 'Speckle Account Password Reset'
type InitializeNewTokenDeps = {
getUserByEmail: typeof getUserByEmail
getUserByEmail: GetUserByEmail
getPendingToken: GetPendingToken
createToken: CreateToken
getServerInfo: typeof getServerInfo
@@ -1,6 +1,6 @@
import { knex, ServerInvites, Streams, Users } from '@/modules/core/dbSchema'
import {
getUserByEmail,
getUserByEmailFactory,
getUserFactory,
UserWithOptionalRole
} from '@/modules/core/repositories/users'
@@ -131,7 +131,7 @@ export const findUserByTargetFactory =
(target: string): Promise<UserWithOptionalRole | null> => {
const { userEmail, userId } = resolveTarget(target)
return userEmail
? getUserByEmail(userEmail, { withRole: true })
? getUserByEmailFactory(deps)(userEmail, { withRole: true })
: getUserFactory(deps)(userId!, { withRole: true })
}