• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

inclusion-numerique / coop-mediation-numerique / 4715219f-1670-4391-924b-02c2cc8255e2

02 Feb 2026 11:34AM UTC coverage: 10.731% (+0.01%) from 10.721%
4715219f-1670-4391-924b-02c2cc8255e2

push

circleci

web-flow
Merge pull request #403 from inclusion-numerique/dev

release

634 of 9659 branches covered (6.56%)

Branch coverage included in aggregate %.

12 of 123 new or added lines in 15 files covered. (9.76%)

4 existing lines in 4 files now uncovered.

2004 of 14923 relevant lines covered (13.43%)

1.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/apps/web/src/server/rpc/user/userRouter.ts
1
import { createHash } from 'node:crypto'
2
import { UtilisateurSetFeatureFlagsValidation } from '@app/web/app/administration/utilisateurs/[id]/UtilisateurSetFeatureFlagsValidation'
3
import { UpdateProfileValidation } from '@app/web/app/user/UpdateProfileValidation'
4
import {
5
  deleteBrevoContact,
6
  deploymentCanDeleteBrevoContact,
7
} from '@app/web/external-apis/brevo/deleteBrevoContact'
8
import { mergeUser } from '@app/web/features/utilisateurs/use-cases/merge/mergeUser'
9
import { nouveauReminders } from '@app/web/features/utilisateurs/use-cases/nouveau-reminders/nouveauReminders'
10
import { searchUser } from '@app/web/features/utilisateurs/use-cases/search/searchUser'
11
import { signupReminders } from '@app/web/features/utilisateurs/use-cases/signup-reminders/signupReminders'
12
import { updateUserFromDataspaceData } from '@app/web/features/utilisateurs/use-cases/update-from-dataspace/updateUserFromDataspaceData'
13
import { prismaClient } from '@app/web/prismaClient'
14
import {
15
  protectedProcedure,
16
  publicProcedure,
17
  router,
18
} from '@app/web/server/rpc/createRouter'
19
import { enforceIsAdmin } from '@app/web/server/rpc/enforceIsAdmin'
20
import { invalidError } from '@app/web/server/rpc/trpcErrors'
21
import { ResetInscriptionUtilisateurValidation } from '@app/web/server/rpc/user/ResetInscriptionUtilisateur'
22
import { UserMergeValidation } from '@app/web/server/rpc/user/userMerge'
23
import { ServerUserSignupValidation } from '@app/web/server/rpc/user/userSignup.server'
24
import { addMutationLog } from '@app/web/utils/addMutationLog'
25
import { fixTelephone } from '@app/web/utils/clean-operations'
26
import { createStopwatch } from '@app/web/utils/stopwatch'
27
import { v4 } from 'uuid'
28
import { z } from 'zod'
29

30
export const userRouter = router({
×
31
  signup: publicProcedure
32
    .input(ServerUserSignupValidation)
33
    .mutation(({ input: { firstName, lastName, email } }) =>
34
      prismaClient.user.create({
×
35
        data: {
36
          id: v4(),
37
          firstName,
38
          lastName,
39
          name: `${firstName} ${lastName}`,
40
          email,
41
        },
42
        select: {
43
          id: true,
44
          email: true,
45
        },
46
      }),
47
    ),
48
  signupReminders: protectedProcedure.mutation(
49
    async ({ ctx: { user: sessionUser } }) => {
50
      enforceIsAdmin(sessionUser)
×
51

52
      await signupReminders()
×
53
    },
54
  ),
55
  inactiveReminders: protectedProcedure.mutation(
56
    async ({ ctx: { user: sessionUser } }) => {
57
      enforceIsAdmin(sessionUser)
×
58

59
      await nouveauReminders()
×
60
    },
61
  ),
62
  updateProfile: protectedProcedure
63
    .input(UpdateProfileValidation)
64
    .mutation(
65
      async ({ input: { firstName, lastName, phone }, ctx: { user } }) => {
66
        const stopwatch = createStopwatch()
×
67
        const updated = await prismaClient.user.update({
×
68
          where: { id: user.id },
69
          data: {
70
            firstName,
71
            lastName,
72
            phone: fixTelephone(phone ?? null),
×
73
            name: `${firstName} ${lastName}`,
74
          },
75
        })
76
        addMutationLog({
×
77
          userId: user.id,
78
          nom: 'ModifierUtilisateur',
79
          duration: stopwatch.stop().duration,
80
          data: {
81
            id: user.id,
82
            firstName,
83
            lastName,
84
            phone,
85
          },
86
        })
87
        return updated
×
88
      },
89
    ),
90
  deleteProfile: protectedProcedure.mutation(async ({ ctx: { user } }) => {
NEW
91
    if (deploymentCanDeleteBrevoContact()) {
×
NEW
92
      await deleteBrevoContact(user.email)
×
93
    }
94

UNCOV
95
    const hash = createHash('sha256')
×
96
      .update(`${user.id}-${user.email}`)
97
      .digest('base64url')
98
      .slice(0, 12)
99

100
    return prismaClient.user.update({
×
101
      where: { id: user.id },
102
      data: {
103
        deleted: new Date(),
104
        email: `deleted+${hash}@coop-numerique.anct.gouv.fr`,
105
        firstName: 'Utilisateur',
106
        lastName: 'Supprimé',
107
        name: 'Utilisateur Supprimé',
108
        phone: null,
109
      },
110
    })
111
  }),
112
  markOnboardingAsSeen: protectedProcedure.mutation(({ ctx: { user } }) =>
113
    prismaClient.user.update({
×
114
      where: { id: user.id },
115
      data: { hasSeenOnboarding: new Date() },
116
    }),
117
  ),
118
  resetInscription: protectedProcedure
119
    .input(ResetInscriptionUtilisateurValidation)
120
    .mutation(async ({ input: { userId }, ctx: { user: sessionUser } }) => {
121
      enforceIsAdmin(sessionUser)
×
122

123
      const stopwatch = createStopwatch()
×
124

125
      const updated = await prismaClient.user.update({
×
126
        where: {
127
          id: userId,
128
          role: 'User',
129
        },
130
        data: {
131
          hasSeenOnboarding: null,
132
          acceptationCgu: null,
133
          inscriptionValidee: null,
134
          donneesConseillerNumeriqueV1Importees: null,
135
          profilInscription: null,
136
          structureEmployeuseRenseignee: null,
137
        },
138
      })
139

140
      if (!updated) {
×
141
        throw invalidError('User not found or user is admin')
×
142
      }
143

144
      addMutationLog({
×
145
        userId,
146
        nom: 'ResetInscription',
147
        duration: stopwatch.stop().duration,
148
        data: {
149
          id: userId,
150
        },
151
      })
152

153
      return updated
×
154
    }),
155
  search: protectedProcedure
156
    .input(
157
      z.object({
158
        query: z.string(),
159
        includeDeleted: z.boolean().optional().default(false),
160
        excludeUserIds: z.array(z.string()).optional().default([]),
161
      }),
162
    )
163
    .query(
164
      ({
165
        input: { query, includeDeleted, excludeUserIds },
166
        ctx: { user: sessionUser },
167
      }) => {
168
        enforceIsAdmin(sessionUser)
×
169

170
        return searchUser({
×
171
          searchParams: {
172
            recherche: query,
173
          },
174
          includeDeleted,
175
          excludeUserIds,
176
        })
177
      },
178
    ),
179
  merge: protectedProcedure
180
    .input(UserMergeValidation)
181
    .mutation(
182
      async ({
183
        input: { sourceUserId, targetUserId },
184
        ctx: { user: sessionUser },
185
      }) => {
186
        enforceIsAdmin(sessionUser)
×
187

188
        return mergeUser(sourceUserId, targetUserId)
×
189
      },
190
    ),
191
  setFeatureFlags: protectedProcedure
192
    .input(UtilisateurSetFeatureFlagsValidation)
193
    .mutation(
194
      async ({
195
        input: { featureFlags, userId },
196
        ctx: { user: sessionUser },
197
      }) => {
198
        enforceIsAdmin(sessionUser)
×
199

200
        const user = await prismaClient.user.findUnique({
×
201
          where: {
202
            id: userId,
203
          },
204
          select: {
205
            id: true,
206
          },
207
        })
208
        if (!user) {
×
209
          throw invalidError('User not found')
×
210
        }
211

212
        const updated = await prismaClient.user.update({
×
213
          where: {
214
            id: userId,
215
          },
216
          data: {
217
            featureFlags,
218
          },
219
          select: {
220
            id: true,
221
            featureFlags: true,
222
          },
223
        })
224

225
        return updated
×
226
      },
227
    ),
228
  updateFromDataspace: protectedProcedure
229
    .input(z.object({ userId: z.string().uuid() }))
230
    .mutation(async ({ input: { userId }, ctx: { user: sessionUser } }) => {
231
      enforceIsAdmin(sessionUser)
×
232

233
      return updateUserFromDataspaceData({ userId })
×
234
    }),
235
})
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc