Skip to content

Commit dc561ca

Browse files
committed
refactor(hooks): merge projectmember and projectrole to project hook
Co-authored-by: William Phetsinorath <william.phetsinorath@shikanime.studio> Signed-off-by: William Phetsinorath <william.phetsinorath-open@interieur.gouv.fr>
1 parent cb6dffc commit dc561ca

17 files changed

Lines changed: 198 additions & 484 deletions

File tree

apps/server/src/resources/project-member/business.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,17 @@ export async function addMember(projectId: Project['id'], user: XOR<{ userId: st
4444
}
4545

4646
await upsertMember({ projectId, userId: userInDb.id, roleIds: [] })
47-
await hook.projectMember.upsert(projectId, userInDb.id)
4847
return listMembers(projectId)
4948
}
5049

5150
export async function patchMembers(projectId: Project['id'], members: typeof projectMemberContract.patchMembers.body._type) {
5251
for (const member of members) {
5352
await upsertMember({ projectId, userId: member.userId, roleIds: member.roles })
54-
await hook.projectMember.upsert(projectId, member.userId)
5553
}
5654
return listMembers(projectId)
5755
}
5856

5957
export async function removeMember(projectId: Project['id'], userId: User['id']) {
60-
await hook.projectMember.delete(projectId, userId)
6158
await deleteMember({ projectId, userId })
6259
return listMembers(projectId)
6360
}

apps/server/src/resources/project-role/business.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
updateRole,
88
} from '@/resources/queries-index.js'
99
import { BadRequest400, Forbidden403, NotFound404 } from '@/utils/errors.js'
10-
import { hook } from '@/utils/hook-wrapper.js'
1110
import prisma from '@/prisma.js'
1211

1312
export async function listRoles(projectId: Project['id']) {
@@ -55,7 +54,6 @@ export async function patchRoles(projectId: Project['id'], roles: typeof project
5554
if (positionsAvailable.length && positionsAvailable.length !== dbRoles.length) return new BadRequest400('Les numéros de position des rôles sont incohérentes')
5655
for (const { id, ...role } of updatedRoles) {
5756
await updateRole(id, role)
58-
await hook.projectRole.upsert(id)
5957
}
6058

6159
return listRoles(projectId)
@@ -78,7 +76,7 @@ export async function createRole(projectId: Project['id'], role: typeof projectR
7876
throw new BadRequest400('oidcGroup doit commencer par /')
7977
}
8078

81-
const createdRole = await prisma.projectRole.create({
79+
await prisma.projectRole.create({
8280
data: {
8381
...role,
8482
projectId,
@@ -88,8 +86,6 @@ export async function createRole(projectId: Project['id'], role: typeof projectR
8886
},
8987
})
9088

91-
await hook.projectRole.upsert(createdRole.id)
92-
9389
return listRoles(projectId)
9490
}
9591

@@ -107,10 +103,10 @@ export async function countRolesMembers(projectId: Project['id']) {
107103

108104
export async function deleteRole(roleId: Project['id']) {
109105
const role = await prisma.projectRole.findUnique({ where: { id: roleId } })
110-
if (role?.type === 'system') {
106+
if (!role) throw new NotFound404()
107+
if (role.type === 'system') {
111108
return new Forbidden403('Ce rôle système ne peut pas être supprimé')
112109
}
113-
await hook.projectRole.delete(roleId)
114110
await deleteRoleQuery(roleId)
115111
return null
116112
}

apps/server/src/resources/project/business.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ export async function createProject(dataDto: typeof projectContract.createProjec
8787
await addLogs({ action: 'Upsert Project Role', data: roleResult.results, userId: requestor.id, requestId, projectId: project.id })
8888
}
8989

90+
for (const member of projectInfos.members) {
91+
const memberResult = await hook.projectMember.upsert(project.id, member.userId)
92+
await addLogs({ action: 'Upsert Project Member', data: memberResult.results, userId: requestor.id, requestId, projectId: project.id })
93+
}
94+
9095
return {
9196
...projectInfos,
9297
clusterIds: projectInfos.clusters.map(({ id }) => id),

apps/server/src/utils/hook-wrapper.spec.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,11 @@ describe('transformToHookProject', () => {
207207
expect(result.users).toEqual([project.owner])
208208

209209
// Assert sur la transformation des rôles
210-
expect(result.roles).toEqual([{ userId: project.owner.id, role: 'owner' }])
210+
expect(result.roles).toEqual([{
211+
name: 'owner',
212+
position: 0,
213+
users: [project.owner],
214+
}])
211215

212216
// Assert sur la transformation des clusters
213217
expect(result.clusters).toEqual([associatedCluster, nonAssociatedCluster].map(({ kubeconfig, ...cluster }) => ({
@@ -218,7 +222,7 @@ describe('transformToHookProject', () => {
218222
})))
219223

220224
// Assert sur la transformation des environnements
221-
expect(result.environments).toEqual(project.environments.map(({ permissions: _, stage, quota, ...environment }) => ({
225+
expect(result.environments).toEqual(project.environments.map(({ permissions: _, stage, quota, ...environment }: any) => ({
222226
quota,
223227
stage: stage.name,
224228
permissions: [{ permissions: { rw: true, ro: true }, userId: project.ownerId }],
@@ -227,9 +231,44 @@ describe('transformToHookProject', () => {
227231
})))
228232

229233
// Assert sur la transformation des repositories
230-
expect(result.repositories).toEqual(project.repositories.map(repo => ({ ...repo, newCreds: mockReposCreds[repo.internalRepoName] })))
234+
expect(result.repositories).toEqual(project.repositories.map((repo: any) => ({ ...repo, newCreds: mockReposCreds[repo.internalRepoName] })))
231235

232236
// Assert sur le store
233237
expect(result.store).toEqual(mockStore)
234238
})
239+
240+
it('handles members with roles correctly', () => {
241+
const roleDev = { id: 'role-dev', name: 'developer', permissions: 0n, position: 0 }
242+
const memberDev = {
243+
userId: 'user-dev',
244+
roleIds: ['role-dev'],
245+
user: { id: 'user-dev', firstName: 'Dev', lastName: 'User', email: 'dev@test.com', createdAt: new Date(), updatedAt: new Date(), adminRoleIds: [] },
246+
id: 'member-dev',
247+
projectId: project.id,
248+
createdAt: new Date(),
249+
updatedAt: new Date(),
250+
}
251+
const projectWithRoles = {
252+
...project,
253+
roles: [roleDev],
254+
members: [memberDev],
255+
}
256+
257+
// @ts-ignore - limited mock
258+
const result = transformToHookProject(projectWithRoles, mockStore, mockReposCreds)
259+
260+
expect(result.roles).toContainEqual({
261+
name: 'owner',
262+
position: 0,
263+
users: [project.owner],
264+
})
265+
expect(result.roles).toContainEqual({
266+
name: 'developer',
267+
permissions: '0',
268+
position: 0,
269+
type: undefined,
270+
oidcGroup: undefined,
271+
users: [memberDev.user],
272+
})
273+
})
235274
})

apps/server/src/utils/hook-wrapper.ts

Lines changed: 16 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { Cluster, Kubeconfig, Project, ProjectRole, Zone, ProjectMembers } from '@prisma/client'
1+
import type { Cluster, Kubeconfig, Project, ProjectRole, Zone } from '@prisma/client'
22
import type { ClusterObject, HookResult, KubeCluster, KubeUser, Project as ProjectPayload, RepoCreds, Repository, Store, ZoneObject } from '@cpn-console/hooks'
33
import { hooks } from '@cpn-console/hooks'
44
import type { AsyncReturnType } from '@cpn-console/shared'
55
import { ProjectAuthorized, getPermsByUserRoles, resourceListToDict } from '@cpn-console/shared'
66
import { genericProxy } from './proxy.js'
7-
import { archiveProject, getAdminPlugin, getAdminRoleById, getClusterByIdOrThrow, getClusterNamesByZoneId, getClustersAssociatedWithProject, getHookProjectInfos, getHookRepository, getProjectStore, getRole, getZoneByIdOrThrow, saveProjectStore, updateProjectClusterHistory, updateProjectCreated, updateProjectFailed, updateProjectWarning } from '@/resources/queries-index.js'
7+
import { archiveProject, getAdminPlugin, getAdminRoleById, getClusterByIdOrThrow, getClusterNamesByZoneId, getClustersAssociatedWithProject, getHookProjectInfos, getHookRepository, getProjectStore, getZoneByIdOrThrow, saveProjectStore, updateProjectClusterHistory, updateProjectCreated, updateProjectFailed, updateProjectWarning } from '@/resources/queries-index.js'
88
import type { ConfigRecords } from '@/resources/project-service/business.js'
99
import { dbToObj } from '@/resources/project-service/business.js'
1010

@@ -139,114 +139,6 @@ const user = {
139139
},
140140
} as const
141141

142-
const projectMember = {
143-
upsert: async (projectId: Project['id'], userId: ProjectMembers['userId']) => {
144-
const project = await getHookProjectInfos(projectId)
145-
const projectStore = dbToObj(await getProjectStore(project.id))
146-
const hookProject = transformToHookProject(project, projectStore)
147-
const store = dbToObj(await getAdminPlugin())
148-
149-
const member = project.members.find(m => m.userId === userId)
150-
if (!member) throw new Error('Member not found')
151-
152-
const memberRoles = project.roles
153-
.filter(role => member.roleIds.includes(role.id))
154-
.map(role => ({
155-
...role,
156-
permissions: role.permissions.toString(),
157-
oidcGroup: role.oidcGroup ?? undefined,
158-
project: hookProject,
159-
}))
160-
161-
const payload = {
162-
userId: member.userId,
163-
roleIds: member.roleIds,
164-
firstName: member.user.firstName,
165-
lastName: member.user.lastName,
166-
email: member.user.email,
167-
type: member.user.type as 'human' | 'bot' | 'ghost',
168-
createdAt: member.user.createdAt.toISOString(),
169-
updatedAt: member.user.updatedAt.toISOString(),
170-
lastLogin: member.user.lastLogin?.toISOString(),
171-
projectId: project.id,
172-
roles: memberRoles,
173-
project: hookProject,
174-
}
175-
176-
return hooks.upsertProjectMember.execute(payload, store)
177-
},
178-
delete: async (projectId: Project['id'], userId: ProjectMembers['userId']) => {
179-
const project = await getHookProjectInfos(projectId)
180-
const projectStore = dbToObj(await getProjectStore(project.id))
181-
const hookProject = transformToHookProject(project, projectStore)
182-
const store = dbToObj(await getAdminPlugin())
183-
184-
const member = project.members.find(m => m.userId === userId)
185-
if (!member) throw new Error('Member not found')
186-
187-
const memberRoles = project.roles
188-
.filter(role => member.roleIds.includes(role.id))
189-
.map(role => ({
190-
...role,
191-
permissions: role.permissions.toString(),
192-
oidcGroup: role.oidcGroup ?? undefined,
193-
project: hookProject,
194-
}))
195-
196-
const payload = {
197-
userId: member.userId,
198-
roleIds: member.roleIds,
199-
firstName: member.user.firstName,
200-
lastName: member.user.lastName,
201-
email: member.user.email,
202-
type: member.user.type as 'human' | 'bot' | 'ghost',
203-
createdAt: member.user.createdAt.toISOString(),
204-
updatedAt: member.user.updatedAt.toISOString(),
205-
lastLogin: member.user.lastLogin?.toISOString(),
206-
projectId: project.id,
207-
roles: memberRoles,
208-
project: hookProject,
209-
}
210-
211-
return hooks.deleteProjectMember.execute(payload, store)
212-
},
213-
} as const
214-
215-
const projectRole = {
216-
upsert: async (roleId: ProjectRole['id']) => {
217-
const role = await getRole(roleId)
218-
if (!role) throw new Error('Role not found')
219-
220-
const project = await getHookProjectInfos(role.projectId)
221-
const projectStore = dbToObj(await getProjectStore(role.projectId))
222-
const hookProject = transformToHookProject(project, projectStore)
223-
224-
const rolePayload = {
225-
...role,
226-
permissions: role.permissions.toString(),
227-
project: hookProject,
228-
}
229-
const store = dbToObj(await getAdminPlugin())
230-
return hooks.upsertProjectRole.execute(rolePayload, store)
231-
},
232-
delete: async (roleId: ProjectRole['id']) => {
233-
const role = await getRole(roleId)
234-
if (!role) throw new Error('Role not found')
235-
236-
const project = await getHookProjectInfos(role.projectId)
237-
const projectStore = dbToObj(await getProjectStore(role.projectId))
238-
const hookProject = transformToHookProject(project, projectStore)
239-
240-
const rolePayload = {
241-
...role,
242-
permissions: role.permissions.toString(),
243-
project: hookProject,
244-
}
245-
const store = dbToObj(await getAdminPlugin())
246-
return hooks.deleteProjectRole.execute(rolePayload, store)
247-
},
248-
} as const
249-
250142
const zone = {
251143
upsert: async (zoneId: Zone['id']) => {
252144
const zone: ZoneObject = await getZoneByIdOrThrow(zoneId)
@@ -299,10 +191,6 @@ export const hook = {
299191
// @ts-ignore TODO voir comment opti la signature de la fonction
300192
project: genericProxy(project, { upsert: ['delete'], delete: ['upsert', 'delete'], getSecrets: ['delete'] }),
301193
// @ts-ignore TODO voir comment opti la signature de la fonction
302-
projectRole: genericProxy(projectRole, { delete: ['upsert', 'delete'], upsert: ['delete'] }),
303-
// @ts-ignore TODO voir comment opti la signature de la fonction
304-
projectMember: genericProxy(projectMember, { delete: ['upsert'], upsert: ['delete'] }),
305-
// @ts-ignore TODO voir comment opti la signature de la fonction
306194
cluster: genericProxy(cluster, { delete: ['upsert', 'delete'], upsert: ['delete'] }),
307195
// @ts-ignore TODO voir comment opti la signature de la fonction
308196
zone: genericProxy(zone, { delete: ['upsert'], upsert: ['delete'] }),
@@ -349,10 +237,20 @@ export function transformToHookProject(project: ProjectInfos, store: Store, repo
349237
store,
350238
users: [project.owner, ...project.members.map(({ user }) => user)],
351239
roles: [
352-
{ userId: project.ownerId, role: 'owner' },
353-
...project.members.map(member => ({
354-
userId: member.userId,
355-
role: 'user' as const,
240+
{
241+
name: 'owner',
242+
position: 0,
243+
users: [project.owner],
244+
},
245+
...project.roles.map(role => ({
246+
name: role.name,
247+
permissions: role.permissions.toString(),
248+
position: role.position,
249+
type: role.type,
250+
oidcGroup: role.oidcGroup,
251+
users: project.members
252+
.filter(member => member.roleIds.includes(role.id))
253+
.map(member => member.user),
356254
})),
357255
],
358256
})

packages/hooks/src/hooks/hook-project-member.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/hooks/src/hooks/hook-project-role.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

packages/hooks/src/hooks/hook-project.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ export interface RepoCreds {
99
}
1010

1111
export interface Role {
12-
userId: string
13-
role: 'owner' | 'user'
12+
name: string
13+
permissions?: string
14+
position: number
15+
type?: string
16+
oidcGroup?: string
17+
users: UserObject[]
1418
}
1519

1620
export interface EnvironmentApis {

packages/hooks/src/hooks/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
export * from './hook-cluster.js'
22
export * from './hook-misc.js'
33
export * from './hook-project.js'
4-
export * from './hook-project-role.js'
5-
export * from './hook-project-member.js'
64
export * from './hook-user.js'
75
export * from './hook-zone.js'
86
export * from './hook-admin-role.js'

plugins/argocd/src/functions.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export const upsertProject: StepCall<Project> = async (payload) => {
4242
...splitExtraRepositories(project.store.argocd?.extraRepositories),
4343
]
4444

45-
await Promise.all([
46-
...project.environments.map(async (environment) => {
45+
await Promise.all(
46+
project.environments.map(async (environment) => {
4747
const nsName = generateNamespaceName(project.id, environment.id)
4848
const cluster = getCluster(project, environment)
4949
const infraProject = await gitlabApi.getOrCreateInfraProject(
@@ -72,7 +72,7 @@ export const upsertProject: StepCall<Project> = async (payload) => {
7272
vaultApi,
7373
)
7474
}),
75-
])
75+
)
7676

7777
await removeInfraEnvValues(project, gitlabApi)
7878

@@ -99,6 +99,7 @@ interface ArgoRepoSource {
9999
path: string
100100
valueFiles: string[]
101101
}
102+
102103
async function ensureInfraEnvValues(
103104
project: Project,
104105
environment: Environment,

0 commit comments

Comments
 (0)