better-auth-organization-member 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js ADDED
@@ -0,0 +1,649 @@
1
+ /** @format */
2
+ import { BASE_ERROR_CODES, defineErrorCodes, } from "better-auth";
3
+ import { createAuthEndpoint, createAuthMiddleware, sessionMiddleware, } from "better-auth/api";
4
+ import { clientSideHasPermission } from "better-auth/client/plugins";
5
+ import { toZodSchema } from "better-auth/db";
6
+ import { getOrgAdapter, } from "better-auth/plugins";
7
+ import { APIError } from "better-call";
8
+ import z from "zod";
9
+ const ORGANIZATION_ERROR_CODES = defineErrorCodes({
10
+ NO_ACTIVE_ORGANIZATION: "No active organization",
11
+ MEMBER_NOT_FOUND: "Member not found",
12
+ ORGANIZATION_NOT_FOUND: "Organization not found",
13
+ YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER: "You are not allowed to update this member",
14
+ YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER: "You cannot leave the organization without an owner",
15
+ INVITATION_NOT_FOUND: "Invitation not found",
16
+ YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_INVITATION: "You are not allowed to update this invitation",
17
+ ONLY_PENDING_INVITATIONS_CAN_BE_UPDATED: "Only pending invitations can be updated",
18
+ YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION: "You are not a member of this organization",
19
+ });
20
+ const getOrganizationPlugin = (ctx) => {
21
+ return ctx.options.plugins?.find((plugin) => plugin.id === "organization");
22
+ };
23
+ export const orgMiddleware = createAuthMiddleware(async () => {
24
+ return {};
25
+ });
26
+ /**
27
+ * The middleware forces the endpoint to require a valid session by utilizing the `sessionMiddleware`.
28
+ * It also appends additional types to the session type regarding organizations.
29
+ */
30
+ export const orgSessionMiddleware = createAuthMiddleware({
31
+ use: [sessionMiddleware],
32
+ }, async (ctx) => {
33
+ const session = ctx.context.session;
34
+ return {
35
+ session,
36
+ };
37
+ });
38
+ /**
39
+ * Organization Member Plugin
40
+ *
41
+ * Extends the organization plugin with:
42
+ * - updateMember endpoint to update member fields (not just role)
43
+ * - Automatically adds afterAcceptInvitation hook to transfer invitation data to member
44
+ *
45
+ * @requires organization plugin
46
+ */
47
+ const createUpdateMemberEndpoint = (options) => {
48
+ // from /better-auth/packages/better-auth/src/plugins/organization/routes/crud-members.ts
49
+ const baseMemberSchema = z.object({
50
+ role: z.union([z.string(), z.array(z.string())]).meta({
51
+ description: 'The new role to be applied. This can be a string or array of strings representing the roles. Eg: ["admin", "sale"]',
52
+ }),
53
+ memberId: z.string().meta({
54
+ description: 'The member id to apply the role update to. Eg: "member-id"',
55
+ }),
56
+ organizationId: z
57
+ .string()
58
+ .meta({
59
+ description: 'An optional organization ID which the member is a part of to apply the role update. If not provided, you must provide session headers to get the active organization. Eg: "organization-id"',
60
+ })
61
+ .optional(),
62
+ // Manually add default member fields
63
+ firstName: z.string().optional().meta({
64
+ description: "First name of the member",
65
+ }),
66
+ lastName: z.string().optional().meta({
67
+ description: "Last name of the member",
68
+ }),
69
+ avatar: z.string().optional().meta({
70
+ description: "Avatar URL of the member",
71
+ }),
72
+ status: z.enum(["active", "inactive"]).optional(),
73
+ });
74
+ const additionalFieldsSchema = toZodSchema({
75
+ fields: options?.schema?.member?.additionalFields || {},
76
+ isClientSide: true,
77
+ });
78
+ // Remove status field from additionalFieldsSchema to prevent overriding the strict enum in baseMemberSchema
79
+ const { status: _status, ...additionalFieldsWithoutStatus } = additionalFieldsSchema.shape;
80
+ return createAuthEndpoint("/organization/update-member", {
81
+ method: "POST",
82
+ body: z.object({
83
+ ...baseMemberSchema.shape,
84
+ ...additionalFieldsWithoutStatus.shape,
85
+ }),
86
+ use: [orgMiddleware, orgSessionMiddleware],
87
+ requireHeaders: true,
88
+ metadata: {
89
+ $Infer: {
90
+ body: {},
91
+ },
92
+ openapi: {
93
+ operationId: "updateOrganizationMember",
94
+ description: "Update a member in an organization",
95
+ responses: {
96
+ "200": {
97
+ description: "Success",
98
+ content: {
99
+ "application/json": {
100
+ schema: {
101
+ type: "object",
102
+ properties: {
103
+ id: {
104
+ type: "string",
105
+ },
106
+ userId: {
107
+ type: "string",
108
+ },
109
+ organizationId: {
110
+ type: "string",
111
+ },
112
+ firstName: {
113
+ type: "string",
114
+ },
115
+ lastName: {
116
+ type: "string",
117
+ },
118
+ avatar: {
119
+ type: "string",
120
+ },
121
+ status: {
122
+ type: "string",
123
+ },
124
+ },
125
+ required: ["id", "userId", "organizationId", "role"],
126
+ },
127
+ },
128
+ },
129
+ },
130
+ },
131
+ },
132
+ },
133
+ }, async (ctx) => {
134
+ const session = ctx.context.session;
135
+ const organizationPlugin = getOrganizationPlugin(ctx.context);
136
+ if (!organizationPlugin) {
137
+ throw new Error("organization-member plugin requires the organization plugin");
138
+ }
139
+ if (!ctx.body.role) {
140
+ throw new APIError("BAD_REQUEST");
141
+ }
142
+ const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
143
+ if (!organizationId) {
144
+ throw new APIError("BAD_REQUEST", {
145
+ message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION,
146
+ });
147
+ }
148
+ const adapter = getOrgAdapter(ctx.context, organizationPlugin.options);
149
+ const roleToSet = Array.isArray(ctx.body.role)
150
+ ? ctx.body.role
151
+ : ctx.body.role
152
+ ? [ctx.body.role]
153
+ : [];
154
+ const member = await adapter.findMemberByOrgId({
155
+ userId: session.user.id,
156
+ organizationId: organizationId,
157
+ });
158
+ if (!member) {
159
+ throw new APIError("BAD_REQUEST", {
160
+ message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND,
161
+ });
162
+ }
163
+ const toBeUpdatedMember = member.id !== ctx.body.memberId
164
+ ? await adapter.findMemberById(ctx.body.memberId)
165
+ : member;
166
+ if (!toBeUpdatedMember) {
167
+ throw new APIError("BAD_REQUEST", {
168
+ message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND,
169
+ });
170
+ }
171
+ const memberBelongsToOrganization = toBeUpdatedMember.organizationId === organizationId;
172
+ if (!memberBelongsToOrganization) {
173
+ throw new APIError("FORBIDDEN", {
174
+ message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER,
175
+ });
176
+ }
177
+ const creatorRole = organizationPlugin.options?.creatorRole || "owner";
178
+ const updatingMemberRoles = member.role.split(",");
179
+ const toBeUpdatedMemberRoles = toBeUpdatedMember.role.split(",");
180
+ const isUpdatingCreator = toBeUpdatedMemberRoles.includes(creatorRole);
181
+ const updaterIsCreator = updatingMemberRoles.includes(creatorRole);
182
+ const isSettingCreatorRole = roleToSet.includes(creatorRole);
183
+ const memberIsUpdatingThemselves = member.id === toBeUpdatedMember.id;
184
+ if ((isUpdatingCreator && !updaterIsCreator) ||
185
+ (isSettingCreatorRole && !updaterIsCreator)) {
186
+ throw new APIError("FORBIDDEN", {
187
+ message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER,
188
+ });
189
+ }
190
+ if (updaterIsCreator && memberIsUpdatingThemselves) {
191
+ const members = await ctx.context.adapter.findMany({
192
+ model: "member",
193
+ where: [
194
+ {
195
+ field: "organizationId",
196
+ value: organizationId,
197
+ },
198
+ ],
199
+ });
200
+ const owners = members.filter((member) => {
201
+ const roles = member.role.split(",");
202
+ return roles.includes(creatorRole);
203
+ });
204
+ if (owners.length <= 1 && !isSettingCreatorRole) {
205
+ throw new APIError("BAD_REQUEST", {
206
+ message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER,
207
+ });
208
+ }
209
+ }
210
+ // currently use clientSideHasPermission instead of hasPermission
211
+ // this does not support dynamic access control yet
212
+ // TODO: improve me
213
+ const canUpdateMember = clientSideHasPermission({
214
+ role: member.role,
215
+ options: organizationPlugin.options,
216
+ permissions: {
217
+ member: ["update"],
218
+ },
219
+ allowCreatorAllPermissions: true,
220
+ });
221
+ if (!canUpdateMember) {
222
+ throw new APIError("FORBIDDEN", {
223
+ message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER,
224
+ });
225
+ }
226
+ // Get organization
227
+ const organization = await adapter.findOrganizationById(organizationId);
228
+ if (!organization) {
229
+ throw new APIError("BAD_REQUEST", {
230
+ message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
231
+ });
232
+ }
233
+ const userBeingUpdated = await ctx.context.internalAdapter.findUserById(toBeUpdatedMember.userId);
234
+ if (!userBeingUpdated) {
235
+ throw new APIError("BAD_REQUEST", {
236
+ message: BASE_ERROR_CODES.USER_NOT_FOUND,
237
+ });
238
+ }
239
+ // Extract updates (exclude memberId and organizationId from body)
240
+ const { memberId: _, organizationId: __, ...updates } = ctx.body;
241
+ let finalUpdates = updates;
242
+ // Run beforeUpdateMember hook (if provided via options parameter)
243
+ if (options?.organizationMemberHooks?.beforeUpdateMember) {
244
+ const response = await options.organizationMemberHooks.beforeUpdateMember({
245
+ member: toBeUpdatedMember,
246
+ updates,
247
+ user: userBeingUpdated,
248
+ organization: organization,
249
+ });
250
+ if (response && typeof response === "object" && "data" in response) {
251
+ finalUpdates = response.data;
252
+ }
253
+ }
254
+ // Update the member - NOTE: this is different from updateMemberRole
255
+ // which only updates the role field. We update any additional fields.
256
+ const updatedMember = await ctx.context.adapter.update({
257
+ model: "member",
258
+ where: [{ field: "id", value: ctx.body.memberId }],
259
+ update: finalUpdates,
260
+ });
261
+ if (!updatedMember) {
262
+ throw new APIError("BAD_REQUEST", {
263
+ message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND,
264
+ });
265
+ }
266
+ // Run afterUpdateMember hook (if provided via options parameter)
267
+ if (options?.organizationMemberHooks?.afterUpdateMember) {
268
+ await options.organizationMemberHooks.afterUpdateMember({
269
+ member: updatedMember,
270
+ user: userBeingUpdated,
271
+ organization: organization,
272
+ });
273
+ }
274
+ return ctx.json(updatedMember);
275
+ });
276
+ };
277
+ const createGetMemberEndpoint = () => {
278
+ return createAuthEndpoint("/organization/get-member", {
279
+ method: "GET",
280
+ query: z.object({
281
+ userId: z.string().meta({
282
+ description: 'The user id of the member to retrieve. Eg: "user-id"',
283
+ }).optional(),
284
+ organizationId: z
285
+ .string()
286
+ .meta({
287
+ description: 'The organization ID which the member belongs to. If not provided, will default to the user\'s active organization. Eg: "organization-id"',
288
+ })
289
+ .optional(),
290
+ organizationSlug: z
291
+ .string()
292
+ .meta({
293
+ description: 'The organization slug to get member for. If not provided, will default to the user\'s active organization. Eg: "organization-slug"',
294
+ })
295
+ .optional(),
296
+ }),
297
+ use: [orgMiddleware, orgSessionMiddleware],
298
+ requireHeaders: true,
299
+ metadata: {
300
+ $Infer: {
301
+ query: {},
302
+ },
303
+ openapi: {
304
+ operationId: "getOrganizationMember",
305
+ description: "Get a member from an organization by user ID",
306
+ responses: {
307
+ "200": {
308
+ description: "Success",
309
+ content: {
310
+ "application/json": {
311
+ schema: {
312
+ type: "object",
313
+ properties: {
314
+ id: {
315
+ type: "string",
316
+ },
317
+ userId: {
318
+ type: "string",
319
+ },
320
+ organizationId: {
321
+ type: "string",
322
+ },
323
+ role: {
324
+ type: "string",
325
+ },
326
+ firstName: {
327
+ type: "string",
328
+ },
329
+ lastName: {
330
+ type: "string",
331
+ },
332
+ avatar: {
333
+ type: "string",
334
+ },
335
+ status: {
336
+ type: "string",
337
+ },
338
+ createdAt: {
339
+ type: "string",
340
+ format: "date-time",
341
+ },
342
+ },
343
+ required: ["id", "userId", "organizationId", "role"],
344
+ },
345
+ },
346
+ },
347
+ },
348
+ },
349
+ },
350
+ },
351
+ }, async (ctx) => {
352
+ const session = ctx.context.session;
353
+ const organizationPlugin = getOrganizationPlugin(ctx.context);
354
+ if (!organizationPlugin) {
355
+ throw new Error("organization-member plugin requires the organization plugin");
356
+ }
357
+ const adapter = getOrgAdapter(ctx.context, organizationPlugin.options);
358
+ // Resolve organizationId from slug if provided
359
+ let organizationId = ctx.query?.organizationId || session.session.activeOrganizationId;
360
+ const userId = ctx.query.userId || session.user.id;
361
+ if (!userId) {
362
+ throw new APIError("BAD_REQUEST", {
363
+ message: BASE_ERROR_CODES.USER_NOT_FOUND,
364
+ });
365
+ }
366
+ if (ctx.query?.organizationSlug) {
367
+ const organization = await adapter.findOrganizationBySlug(ctx.query.organizationSlug);
368
+ if (!organization) {
369
+ throw new APIError("BAD_REQUEST", {
370
+ message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
371
+ });
372
+ }
373
+ organizationId = organization.id;
374
+ }
375
+ if (!organizationId) {
376
+ throw new APIError("BAD_REQUEST", {
377
+ message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION,
378
+ });
379
+ }
380
+ // Verify the current user is a member of the organization
381
+ const currentUserMember = await adapter.findMemberByOrgId({
382
+ userId: session.user.id,
383
+ organizationId: organizationId,
384
+ });
385
+ if (!currentUserMember) {
386
+ throw new APIError("FORBIDDEN", {
387
+ message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION,
388
+ });
389
+ }
390
+ // Return current user member if the query is empty
391
+ if (!ctx.query.userId && (ctx.query.organizationSlug || ctx.query.organizationId)) {
392
+ return ctx.json(currentUserMember);
393
+ }
394
+ // Get the requested member by userId and organizationId
395
+ const requestedMember = await adapter.findMemberByOrgId({
396
+ userId: userId,
397
+ organizationId: organizationId,
398
+ });
399
+ if (!requestedMember) {
400
+ throw new APIError("BAD_REQUEST", {
401
+ message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND,
402
+ });
403
+ }
404
+ return ctx.json(requestedMember);
405
+ });
406
+ };
407
+ const createUpdateInvitationEndpoint = (options) => {
408
+ const baseInvitationSchema = z.object({
409
+ invitationId: z.string().meta({
410
+ description: 'The invitation id to update. Eg: "invitation-id"',
411
+ }),
412
+ organizationId: z
413
+ .string()
414
+ .meta({
415
+ description: 'An optional organization ID which the invitation belongs to. If not provided, you must provide session headers to get the active organization. Eg: "organization-id"',
416
+ })
417
+ .optional(),
418
+ });
419
+ const additionalFieldsSchema = toZodSchema({
420
+ fields: options?.schema?.invitation?.additionalFields || {},
421
+ isClientSide: true,
422
+ });
423
+ return createAuthEndpoint("/organization/update-invitation", {
424
+ method: "POST",
425
+ body: z.object({
426
+ ...baseInvitationSchema.shape,
427
+ ...additionalFieldsSchema.shape,
428
+ role: z
429
+ .union([z.string(), z.array(z.string())])
430
+ .optional()
431
+ .meta({
432
+ description: 'The new role to be applied. This can be a string or array of strings representing the roles. Eg: ["admin", "sale"]',
433
+ }),
434
+ }),
435
+ use: [orgMiddleware, orgSessionMiddleware],
436
+ requireHeaders: true,
437
+ metadata: {
438
+ $Infer: {
439
+ body: {},
440
+ },
441
+ openapi: {
442
+ operationId: "updateOrganizationInvitation",
443
+ description: "Update a pending invitation in an organization",
444
+ responses: {
445
+ "200": {
446
+ description: "Success",
447
+ content: {
448
+ "application/json": {
449
+ schema: {
450
+ type: "object",
451
+ properties: {
452
+ id: {
453
+ type: "string",
454
+ },
455
+ organizationId: {
456
+ type: "string",
457
+ },
458
+ email: {
459
+ type: "string",
460
+ },
461
+ role: {
462
+ type: "string",
463
+ },
464
+ status: {
465
+ type: "string",
466
+ },
467
+ createdAt: {
468
+ type: "string",
469
+ format: "date-time",
470
+ description: "Timestamp when the team was created",
471
+ },
472
+ updatedAt: {
473
+ type: "string",
474
+ format: "date-time",
475
+ description: "Timestamp when the team was last updated",
476
+ },
477
+ },
478
+ required: [
479
+ "id",
480
+ "organizationId",
481
+ "email",
482
+ "role",
483
+ "status",
484
+ ],
485
+ },
486
+ },
487
+ },
488
+ },
489
+ },
490
+ },
491
+ },
492
+ }, async (ctx) => {
493
+ const session = ctx.context.session;
494
+ const organizationPlugin = getOrganizationPlugin(ctx.context);
495
+ if (!organizationPlugin) {
496
+ throw new Error("organization-member plugin requires the organization plugin");
497
+ }
498
+ const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
499
+ if (!organizationId) {
500
+ throw new APIError("BAD_REQUEST", {
501
+ message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION,
502
+ });
503
+ }
504
+ const adapter = getOrgAdapter(ctx.context, organizationPlugin.options);
505
+ // Get the current user's member record
506
+ const member = await adapter.findMemberByOrgId({
507
+ userId: session.user.id,
508
+ organizationId: organizationId,
509
+ });
510
+ if (!member) {
511
+ throw new APIError("BAD_REQUEST", {
512
+ message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND,
513
+ });
514
+ }
515
+ // Check permissions - user must have permission to manage invitations
516
+ const canManageInvitations = clientSideHasPermission({
517
+ role: member.role,
518
+ options: organizationPlugin.options,
519
+ permissions: {
520
+ invitation: ["create"],
521
+ },
522
+ allowCreatorAllPermissions: true,
523
+ });
524
+ if (!canManageInvitations) {
525
+ throw new APIError("FORBIDDEN", {
526
+ message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_INVITATION,
527
+ });
528
+ }
529
+ // Get the invitation
530
+ const invitation = await adapter.findInvitationById(ctx.body.invitationId);
531
+ if (!invitation) {
532
+ throw new APIError("BAD_REQUEST", {
533
+ message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND,
534
+ });
535
+ }
536
+ // Verify invitation belongs to the organization
537
+ if (invitation.organizationId !== organizationId) {
538
+ throw new APIError("FORBIDDEN", {
539
+ message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_INVITATION,
540
+ });
541
+ }
542
+ // Only allow updating pending invitations
543
+ if (invitation.status !== "pending") {
544
+ throw new APIError("BAD_REQUEST", {
545
+ message: ORGANIZATION_ERROR_CODES.ONLY_PENDING_INVITATIONS_CAN_BE_UPDATED,
546
+ });
547
+ }
548
+ // Get organization
549
+ const organization = await adapter.findOrganizationById(organizationId);
550
+ if (!organization) {
551
+ throw new APIError("BAD_REQUEST", {
552
+ message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
553
+ });
554
+ }
555
+ // Extract updates (exclude invitationId and organizationId from body)
556
+ const { invitationId: _, organizationId: __, ...updates } = ctx.body;
557
+ let finalUpdates = updates;
558
+ // Run beforeUpdateInvitation hook (if provided via options parameter)
559
+ if (options?.organizationMemberHooks?.beforeUpdateInvitation) {
560
+ const response = await options.organizationMemberHooks.beforeUpdateInvitation({
561
+ invitation,
562
+ updates,
563
+ inviter: session.user,
564
+ organization,
565
+ });
566
+ if (response && typeof response === "object" && "data" in response) {
567
+ finalUpdates = response.data;
568
+ }
569
+ }
570
+ // Update the invitation
571
+ const updatedInvitation = await ctx.context.adapter.update({
572
+ model: "invitation",
573
+ where: [{ field: "id", value: ctx.body.invitationId }],
574
+ update: finalUpdates,
575
+ });
576
+ if (!updatedInvitation) {
577
+ throw new APIError("BAD_REQUEST", {
578
+ message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND,
579
+ });
580
+ }
581
+ // Run afterUpdateInvitation hook (if provided via options parameter)
582
+ if (options?.organizationMemberHooks?.afterUpdateInvitation) {
583
+ await options.organizationMemberHooks.afterUpdateInvitation({
584
+ invitation: updatedInvitation,
585
+ inviter: session.user,
586
+ organization,
587
+ });
588
+ }
589
+ return ctx.json(updatedInvitation);
590
+ });
591
+ };
592
+ export const organizationMember = (userConfig) => {
593
+ const config = {
594
+ ...userConfig,
595
+ };
596
+ return {
597
+ id: "organization-member",
598
+ init(ctx) {
599
+ const organizationPlugin = getOrganizationPlugin(ctx);
600
+ if (!organizationPlugin) {
601
+ throw new Error("organization-member plugin requires the organization plugin");
602
+ }
603
+ // Get or create organizationHooks
604
+ if (!organizationPlugin.options.organizationHooks) {
605
+ organizationPlugin.options.organizationHooks = {};
606
+ }
607
+ const existingAfterAcceptInvitation = organizationPlugin.options.organizationHooks?.afterAcceptInvitation;
608
+ organizationPlugin.options.organizationHooks.afterAcceptInvitation =
609
+ async (data) => {
610
+ await existingAfterAcceptInvitation?.(data);
611
+ try {
612
+ const { invitation, member } = data;
613
+ const updateData = {};
614
+ // TODO improve me
615
+ for (const field of Object.keys(organizationPlugin.options?.schema?.invitation
616
+ ?.additionalFields ?? {})) {
617
+ const fieldName = organizationPlugin.options?.schema?.invitation
618
+ ?.additionalFields?.[field]?.fieldName || field;
619
+ updateData[fieldName] = invitation[fieldName];
620
+ }
621
+ // Only update if there are fields to transfer
622
+ if (Object.keys(updateData).length > 0) {
623
+ await ctx.adapter.update({
624
+ model: "member",
625
+ where: [{ field: "id", value: member.id }],
626
+ update: updateData,
627
+ });
628
+ ctx.logger?.info("Member fields updated from invitation:", {
629
+ memberId: member.id,
630
+ fields: Object.keys(updateData),
631
+ });
632
+ }
633
+ }
634
+ catch (error) {
635
+ ctx.logger?.error("Failed to update member fields from invitation:", error);
636
+ // Don't throw - let the invitation acceptance succeed
637
+ }
638
+ };
639
+ },
640
+ endpoints: {
641
+ getMember: createGetMemberEndpoint(),
642
+ updateMember: createUpdateMemberEndpoint(userConfig),
643
+ updateInvitation: createUpdateInvitationEndpoint(userConfig),
644
+ },
645
+ options: userConfig,
646
+ $ERROR_CODES: ORGANIZATION_ERROR_CODES,
647
+ };
648
+ };
649
+ //# sourceMappingURL=server.js.map