Skip to content

Commit

Permalink
feat: ✨ add users search filter (admin)
Browse files Browse the repository at this point in the history
  • Loading branch information
ArnaudTA committed Sep 17, 2024
1 parent e449e31 commit cc1ab56
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 19 deletions.
2 changes: 1 addition & 1 deletion apps/client/src/components/AdminRoleForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ async function switchUserMembership(checked: boolean, user: User, fromSuggestion
}
}
onBeforeMount(async () => {
users.value = await usersStore.listUsers({ adminRoleId: role.value.id })
users.value = await usersStore.listUsers({ adminRoleIds: role.value.id })
})
</script>

Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/stores/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { userContract } from '@cpn-console/shared'
import { apiClient, extractData } from '@/api/xhr-client.js'

export const useUsersStore = defineStore('users', () => {
const listUsers = async (query: typeof userContract.getAllUsers.query._type) =>
const listUsers = async (query: typeof userContract.getAllUsers.query._input) =>
apiClient.Users.getAllUsers({ query })
.then(res => extractData(res, 200))

Expand Down
3 changes: 1 addition & 2 deletions apps/client/src/views/admin/AdminRoles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ onBeforeMount(async () => {
</script>

<template>
<DsoSelectedProject />
<div
class="flex flex-row"
>
Expand All @@ -70,7 +69,7 @@ onBeforeMount(async () => {
data-testid="addRoleBtn"
:class="selectedId ? 'w-11/12' : ''"
secondary
@click="addRole()"
@click="addRole"
/>
<button
v-for="role in roleList"
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/resources/user/business.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ describe('test users business', () => {
await getUsers({})

expect(getUsersQueryMock).toHaveBeenCalledTimes(1)
expect(getUsersQueryMock).toHaveBeenCalledWith({})
expect(getUsersQueryMock).toHaveBeenCalledWith({ AND: [] })
})
it('should query with filter adminRoleIds', async () => {
prisma.user.update.mockResolvedValue(null)

await getUsers({ adminRoleId })
await getUsers({ adminRoleIds: [adminRoleId] })

expect(getUsersQueryMock).toHaveBeenCalledTimes(1)
expect(getUsersQueryMock).toHaveBeenCalledWith({ adminRoleIds: { has: adminRoleId } })
expect(getUsersQueryMock).toHaveBeenCalledWith({ AND: [{ adminRoleIds: { hasSome: [adminRoleId] } }] })
})
})

Expand Down
27 changes: 22 additions & 5 deletions apps/server/src/resources/user/business.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@ import { getMatchingUsers as getMatchingUsersQuery, getUsers as getUsersQuery }
import prisma from '@/prisma.js'
import type { UserDetails } from '@/types/index.js'

export function getUsers(query: typeof userContract.getAllUsers.query._type) {
const where: Prisma.UserWhereInput = {}
if (query.adminRoleId) {
where.adminRoleIds = { has: query.adminRoleId }
export async function getUsers(query: typeof userContract.getAllUsers.query._type, relationType: 'OR' | 'AND' = 'AND') {
const adminRoleIds = [
...query.adminRoleIds ?? [],
...query.adminRoles
? (await prisma.adminRole.findMany({ where: { name: { in: query.adminRoles } } }))
.map(({ id }) => id)
: [],
]

const whereInputs: Prisma.UserWhereInput[] = []
if (adminRoleIds.length) {
whereInputs.push({ adminRoleIds: { hasSome: adminRoleIds } })
}
if (query.memberOfIds) {
whereInputs.push({
OR: [
{ projectsOwned: { some: { id: { in: query.memberOfIds } } } },
{ ProjectMembers: { some: { project: { id: { in: query.memberOfIds } } } } },
],
})
}
return getUsersQuery(where)

return getUsersQuery({ [relationType]: whereInputs })
}

export async function getMatchingUsers(query: typeof userContract.getMatchingUsers.query._type) {
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/resources/user/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ export function userRouter() {
}
},

getAllUsers: async ({ request: req, query }) => {
getAllUsers: async ({ request: req, query: { relationType, ...query } }) => {
const perms = await authUser(req)

if (!AdminAuthorized.isAdmin(perms.adminPermissions)) return new Forbidden403()

const body = await getUsers(query)
const body = await getUsers(query, relationType)

return {
status: 200,
Expand Down
5 changes: 2 additions & 3 deletions packages/shared/src/contracts/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { z } from 'zod'
import { apiPrefix, contractInstance } from '../api-client.js'
import {
GetMatchingUsersSchema,
UserQuerySchema,
UserSchema,
} from '../schemas/index.js'
import { ErrorSchema } from '../schemas/utils.js'
Expand Down Expand Up @@ -38,9 +39,7 @@ export const userContract = contractInstance.router({
path: `${apiPrefix}/users`,
summary: 'Get all users',
description: 'Get all users.',
query: z.object({
adminRoleId: z.string().uuid().optional(),
}),
query: UserQuerySchema,
responses: {
200: z.lazy(() => UserSchema.array()),
400: ErrorSchema,
Expand Down
10 changes: 9 additions & 1 deletion packages/shared/src/schemas/role.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { z } from 'zod'
import { permissionLevelSchema } from '../utils/permissions.js'

export const RoleNameSchema = z.string().max(30)

export const RoleSchema = z.object({
id: z.string().uuid(),
name: z.string().max(30),
name: RoleNameSchema,
permissions: permissionLevelSchema,
position: z.number().min(0),
})
Expand All @@ -16,6 +18,12 @@ export const AdminRoleSchema = RoleSchema.extend({
oidcGroup: z.string(),
})

export const RoleNameCsvSchema = z.string()
.refine((value) => {
return !value.split(',').some(name => !RoleNameSchema.safeParse(name).success)
})
.transform(value => value.split(','))

export type Role = Zod.infer<typeof RoleSchema>
export type RoleBigint = Omit<Zod.infer<typeof RoleSchema>, 'permissions'> & { permissions: bigint }
export type AdminRole = Zod.infer<typeof AdminRoleSchema>
Expand Down
14 changes: 13 additions & 1 deletion packages/shared/src/schemas/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from 'zod'
import { AtDatesToStringExtend, ErrorSchema } from './utils.js'
import { AtDatesToStringExtend, ErrorSchema, UuidOrCsvUuidSchema } from './utils.js'
import { RoleNameCsvSchema } from './role.js'

export const UserSchema = z.object({
id: z.string()
Expand Down Expand Up @@ -108,3 +109,14 @@ export const UpdateUserAdminRoleSchema = {
500: ErrorSchema,
},
}

export const UserQuerySchema = z.object({
adminRoles: RoleNameCsvSchema
.optional(),
adminRoleIds: UuidOrCsvUuidSchema
.optional(),
memberOfIds: UuidOrCsvUuidSchema
.optional(),
relationType: z.enum(['OR', 'AND'])
.optional(),
})
8 changes: 8 additions & 0 deletions packages/shared/src/schemas/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ export const AtDatesToStringExtend = {
updatedAt: dateToString,
createdAt: dateToString,
}

export const UuidOrCsvUuidSchema = z.string()
.refine((value) => {
return !value
.split(',')
.some(uuid => !z.string().uuid().safeParse(uuid).success)
})
.transform(value => value.split(','))

0 comments on commit cc1ab56

Please sign in to comment.