Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/platform/platform-role/platform.role.resolver.mutations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RoleChangeType } from '@alkemio/notifications-lib';
import { LogContext } from '@common/enums';
import { AuthorizationPrivilege } from '@common/enums/authorization.privilege';
import { LicensingCredentialBasedCredentialType } from '@common/enums/licensing.credential.based.credential.type';
import { RoleName } from '@common/enums/role.name';
Expand All @@ -14,12 +15,14 @@ import { UserLookupService } from '@domain/community/user-lookup/user.lookup.ser
import { AccountService } from '@domain/space/account/account.service';
import { AccountLicenseService } from '@domain/space/account/account.service.license';
import { AccountLookupService } from '@domain/space/account.lookup/account.lookup.service';
import { Inject, LoggerService } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { PlatformService } from '@platform/platform/platform.service';
import { NotificationInputPlatformGlobalRoleChange } from '@services/adapters/notification-adapter/dto/platform/notification.dto.input.platform.global.role.change';
import { NotificationPlatformAdapter } from '@services/adapters/notification-adapter/notification.platform.adapter';
import { InstrumentResolver } from '@src/apm/decorators';
import { CurrentActor } from '@src/common/decorators';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { AssignPlatformRoleInput } from './dto/platform.role.dto.assign';
import { RemovePlatformRoleInput } from './dto/platform.role.dto.remove';

Expand All @@ -37,7 +40,8 @@ export class PlatformRoleResolverMutations {
private roleSetService: RoleSetService,
private userLookupService: UserLookupService,
private roleSetAuthorizationService: RoleSetAuthorizationService,
private platformService: PlatformService
private platformService: PlatformService,
@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
) {}

@Mutation(() => IUser, {
Expand Down Expand Up @@ -195,8 +199,23 @@ export class PlatformRoleResolverMutations {
type: type,
role: role,
};
await this.notificationPlatformAdapter.platformGlobalRoleChanged(
notificationInput
);
// Both call sites above invoke this WITHOUT `await`, so anything this
// rejects with becomes an unhandled rejection — and under Node's default
// `--unhandled-rejections=throw` that terminates the process rather than
// failing the request. Observed twice in live verification: a user row
// with a null profile made the payload builder throw, and the server
// exited mid-run. Notifying is best-effort by design; it must never be
// able to take the process down, whatever the adapter does next.
try {
await this.notificationPlatformAdapter.platformGlobalRoleChanged(
notificationInput
);
} catch (error: any) {
this.logger.error(
`Unable to dispatch platform global role change notification (user=${user.id}, role=${role}, type=${type}): ${error?.message}`,
error?.stack,
LogContext.NOTIFICATIONS
);
Comment on lines +202 to +218
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ export class NotificationExternalAdapter {
);
const result: NotificationEventPayloadPlatformUserRemoved = {
user: {
displayName: user.profile.displayName,
displayName: this.resolveUserDisplayName(user),
email: user.email,
},
...basePayload,
Expand Down Expand Up @@ -1237,7 +1237,7 @@ export class NotificationExternalAdapter {
lastName: user.lastName,
email: user.email,
profile: {
displayName: user.profile.displayName,
displayName: this.resolveUserDisplayName(user),
url: userURL,
},
type: ActorType.USER,
Expand All @@ -1252,13 +1252,38 @@ export class NotificationExternalAdapter {
lastName: user.lastName,
email: user.email,
profile: {
displayName: user.profile.displayName,
displayName: this.resolveUserDisplayName(user),
url: this.urlGeneratorService.createUrlForUserNameID(user.nameID),
},
type: ActorType.USER,
};
}

/**
* A notification payload must never be the thing that takes a request — or
* the process — down.
*
* Every caller of the three payload builders that read `profile.displayName`
* loads the user with `relations: { profile: true }`, so a null profile means
* the ROW is incomplete, not that the relation was forgotten. Dereferencing
* it unguarded turned one such row into a `TypeError`, and because
* `notifyPlatformGlobalRoleChange` invokes its builder without `await` and
* without a catch, that rejection reached Node's default
* `--unhandled-rejections=throw` and killed the server outright — observed
* twice during live verification of workspace#027-platform-role-redesign,
* once via role revocation and once via `createDiscussion`.
*
* Falls back to the user's name, then their email, so the notification still
* carries a usable human identifier instead of failing to send.
*/
private resolveUserDisplayName(user: IUser): string {
return (
user.profile?.displayName ||
`${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() ||
user.email
);
Comment on lines +1279 to +1284
}

private getPlatformURL(): string {
return this.configService.get('hosting.endpoint_cluster', { infer: true });
}
Expand Down
Loading