Skip to content

Commit

Permalink
feat: add user to main channel; fix deleted user issues (#947)
Browse files Browse the repository at this point in the history
  • Loading branch information
nunocaseiro authored Jan 27, 2023
1 parent a2f753c commit e2a5232
Show file tree
Hide file tree
Showing 24 changed files with 254 additions and 51 deletions.
3 changes: 2 additions & 1 deletion backend/src/modules/azure/azure.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import AuthModule from '../auth/auth.module';
import { CommunicationModule } from '../communication/communication.module';
import UsersModule from '../users/users.module';
import { authAzureApplication, authAzureService, cronAzureService } from './azure.providers';
import AzureController from './controller/azure.controller';

@Module({
imports: [UsersModule, AuthModule],
imports: [UsersModule, AuthModule, CommunicationModule],
controllers: [AzureController],
providers: [cronAzureService, authAzureService, authAzureApplication]
})
Expand Down
8 changes: 7 additions & 1 deletion backend/src/modules/azure/services/auth.azure.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import * as UserType from 'src/modules/users/interfaces/types';
import { AuthAzureService } from '../interfaces/services/auth.azure.service.interface';
import { CronAzureService } from '../interfaces/services/cron.azure.service.interface';
import { TYPES } from '../interfaces/types';
import * as CommunicationsType from 'src/modules/communication/interfaces/types';
import { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface';

type AzureUserFound = {
mail?: string;
Expand All @@ -36,7 +38,9 @@ export default class AuthAzureServiceImpl implements AuthAzureService {
@Inject(AuthType.TYPES.services.GetTokenAuthService)
private readonly getTokenService: GetTokenAuthService,
@Inject(TYPES.services.CronAzureService)
private readonly cronAzureService: CronAzureService
private readonly cronAzureService: CronAzureService,
@Inject(CommunicationsType.TYPES.services.SlackCommunicationService)
private slackCommunicationService: CommunicationServiceInterface
) {}

async loginOrRegisterAzureToken(azureToken: string) {
Expand Down Expand Up @@ -66,6 +70,8 @@ export default class AuthAzureServiceImpl implements AuthAzureService {
providerAccountCreatedAt: userFromAzure.value[0].createdDateTime
});

this.slackCommunicationService.executeAddUserMainChannel({ email });

if (!createdUser) return null;

return signIn(createdUser, this.getTokenService, 'azure');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Logger } from '@nestjs/common';
import { ConfigurationType } from 'src/modules/communication/dto/types';
import { ConversationsHandlerInterface } from 'src/modules/communication/interfaces/conversations.handler.interface';
import { UsersHandlerInterface } from 'src/modules/communication/interfaces/users.handler.interface';
import { AddUserIntoChannelApplicationInterface } from '../interfaces/communication.application.interface copy';

export class SlackAddUserIntoChannelApplication implements AddUserIntoChannelApplicationInterface {
private logger = new Logger(SlackAddUserIntoChannelApplication.name);

constructor(
private readonly config: ConfigurationType,
private readonly conversationsHandler: ConversationsHandlerInterface,
private readonly usersHandler: UsersHandlerInterface
) {}

public execute(email: string): Promise<boolean> {
return this.inviteMemberToMainChannel(email);
}

private async inviteMemberToMainChannel(email: string): Promise<boolean> {
try {
const userId = await this.usersHandler.getSlackUserIdByEmail(email);
await this.conversationsHandler.inviteUserToChannel(this.config.slackMasterChannelId, userId);
} catch (e) {
return false;
}

return true;
}
}
21 changes: 19 additions & 2 deletions backend/src/modules/communication/communication.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { BullModule } from '@nestjs/bull';
import { forwardRef, Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { configuration } from 'src/infrastructure/config/configuration';
import BoardsModule from 'src/modules/boards/boards.module';
import {
AddUserIntoChannelApplication,
ArchiveChannelApplication,
ArchiveChannelService,
ChatHandler,
Expand All @@ -18,8 +19,10 @@ import { SlackArchiveChannelConsumer } from 'src/modules/communication/consumers
import { SlackCommunicationConsumer } from 'src/modules/communication/consumers/slack-communication.consumer';
import { SlackArchiveChannelProducer } from 'src/modules/communication/producers/slack-archive-channel.producer';
import { SlackCommunicationProducer } from 'src/modules/communication/producers/slack-communication.producer';
import { SlackAddUserToChannelConsumer } from './consumers/slack-add-user-channel.consummer';
import { SlackMergeBoardConsumer } from './consumers/slack-merge-board.consumer';
import { SlackResponsibleConsumer } from './consumers/slack-responsible.consumer';
import { SlackAddUserToChannelProducer } from './producers/slack-add-user-channel.producer';
import { SlackMergeBoardProducer } from './producers/slack-merge-board.producer';
import { SlackResponsibleProducer } from './producers/slack-responsible.producer';

Expand Down Expand Up @@ -71,6 +74,17 @@ import { SlackResponsibleProducer } from './producers/slack-responsible.producer
removeOnComplete: SlackArchiveChannelProducer.REMOVE_ON_COMPLETE,
priority: SlackArchiveChannelProducer.PRIORITY
}
}),
BullModule.registerQueue({
name: SlackAddUserToChannelProducer.QUEUE_NAME,
defaultJobOptions: {
attempts: SlackAddUserToChannelProducer.ATTEMPTS,
backoff: SlackAddUserToChannelProducer.BACKOFF,
delay: SlackAddUserToChannelProducer.DELAY,
removeOnFail: SlackAddUserToChannelProducer.REMOVE_ON_FAIL,
removeOnComplete: SlackAddUserToChannelProducer.REMOVE_ON_COMPLETE,
priority: SlackAddUserToChannelProducer.PRIORITY
}
})
]
: [])
Expand All @@ -88,14 +102,17 @@ import { SlackResponsibleProducer } from './producers/slack-responsible.producer
ArchiveChannelApplication,
ResponsibleApplication,
MergeBoardApplication,
AddUserIntoChannelApplication,
SlackCommunicationProducer,
SlackCommunicationConsumer,
SlackResponsibleProducer,
SlackResponsibleConsumer,
SlackMergeBoardProducer,
SlackMergeBoardConsumer,
SlackArchiveChannelProducer,
SlackArchiveChannelConsumer
SlackArchiveChannelConsumer,
SlackAddUserToChannelConsumer,
SlackAddUserToChannelProducer
]
: [])
],
Expand Down
22 changes: 22 additions & 0 deletions backend/src/modules/communication/communication.providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { UsersHandlerInterface } from 'src/modules/communication/interfaces/user
import { SlackArchiveChannelService } from 'src/modules/communication/services/slack-archive-channel.service';
import { SlackCommunicationService } from 'src/modules/communication/services/slack-communication.service';
import { SlackDisabledCommunicationService } from 'src/modules/communication/services/slack-disabled-communication.service';
import { SlackAddUserIntoChannelApplication } from './applications/slack-add-user-channel.application';
import { UsersSlackHandler } from './handlers/users-slack.handler';

export const CommunicationGateAdapter = {
Expand Down Expand Up @@ -83,6 +84,27 @@ export const CommunicationApplication = {
inject: [ConfigService, ConversationsSlackHandler, UsersSlackHandler, ChatSlackHandler]
};

export const AddUserIntoChannelApplication = {
provide: TYPES.application.SlackAddUserIntoChannelApplication,
useFactory: (
configService: ConfigService,
conversationsHandler: ConversationsHandlerInterface,
usersHandler: UsersHandlerInterface
) => {
return new SlackAddUserIntoChannelApplication(
{
slackApiBotToken: configService.getOrThrow(SLACK_API_BOT_TOKEN),
slackMasterChannelId: configService.getOrThrow(SLACK_MASTER_CHANNEL_ID),
slackChannelPrefix: configService.getOrThrow(SLACK_CHANNEL_PREFIX),
frontendUrl: configService.getOrThrow(FRONTEND_URL)
},
conversationsHandler,
usersHandler
);
},
inject: [ConfigService, ConversationsSlackHandler, UsersSlackHandler]
};

export const ArchiveChannelApplication = {
provide: TYPES.application.SlackArchiveChannelApplication,
useFactory: (conversationsHandler: ConversationsHandlerInterface) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { OnQueueCompleted, Process, Processor } from '@nestjs/bull';
import { Inject, Logger } from '@nestjs/common';
import { Job } from 'bull';
import { AddUserMainChannelType } from 'src/modules/communication/dto/types';
import { TYPES } from 'src/modules/communication/interfaces/types';
import { AddUserIntoChannelApplicationInterface } from '../interfaces/communication.application.interface copy';
import { SlackAddUserToChannelProducer } from '../producers/slack-add-user-channel.producer';
import { SlackCommunicationEventListeners } from './slack-communication-event-listeners';

@Processor(SlackAddUserToChannelProducer.QUEUE_NAME)
export class SlackAddUserToChannelConsumer extends SlackCommunicationEventListeners<
AddUserMainChannelType,
boolean
> {
constructor(
@Inject(TYPES.application.SlackAddUserIntoChannelApplication)
private readonly application: AddUserIntoChannelApplicationInterface
) {
const logger = new Logger(SlackAddUserToChannelConsumer.name);
super(logger);
}

@Process()
override async communication(job: Job<AddUserMainChannelType>) {
const { email } = job.data;

this.logger.verbose(
`execute communication for adding user with email: "${email}" and Job id: "${job.id}" (pid ${process.pid})`
);

const result = await this.application.execute(email);

return result;
}

// https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#events
@OnQueueCompleted()
override async onCompleted(job: Job<AddUserMainChannelType>, result: boolean[]) {
this.logger.verbose(
`Completed Job id: "${job.id}". User with email: ${job.data.email} was ${
!result[0] ? 'not' : ''
} added to the main channel`
);
this.saveLog(
`User with email: ${job.data.email} was ${!result[0] ? 'not' : ''} added to the main channel`
);
}
}
4 changes: 4 additions & 0 deletions backend/src/modules/communication/dto/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,7 @@ export type ArchiveChannelData = {
data: PartialBoardType | string;
cascade?: boolean;
};

export type AddUserMainChannelType = {
email: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface AddUserIntoChannelApplicationInterface {
execute(email: string): Promise<boolean>;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { BoardType, ChangeResponsibleType, MergeBoardType } from '../dto/types';
import {
AddUserMainChannelType,
BoardType,
ChangeResponsibleType,
MergeBoardType
} from '../dto/types';

export interface CommunicationServiceInterface {
execute(board: BoardType): Promise<void>;
executeResponsibleChange(changeResponsibleDto: ChangeResponsibleType): Promise<void>;
executeMergeBoardNotification(mergeBoard: MergeBoardType): Promise<void>;
executeAddUserMainChannel(user: AddUserMainChannelType): Promise<void>;
}
3 changes: 2 additions & 1 deletion backend/src/modules/communication/interfaces/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ export const TYPES = {
SlackCommunicationApplication: 'SlackCommunicationApplication',
SlackMergeBoardApplication: 'SlackMergeBoardApplication',
SlackResponsibleApplication: 'SlackResponsibleApplication',
SlackArchiveChannelApplication: 'SlackArchiveChannelApplication'
SlackArchiveChannelApplication: 'SlackArchiveChannelApplication',
SlackAddUserIntoChannelApplication: 'SlackAddUserIntoChannelApplication'
},
services: {
SlackCommunicationService: 'SlackCommunicationService',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { InjectQueue } from '@nestjs/bull';
import { Injectable, Logger } from '@nestjs/common';
import { Job, Queue } from 'bull';
import { AddUserMainChannelType } from 'src/modules/communication/dto/types';

@Injectable()
export class SlackAddUserToChannelProducer {
private logger = new Logger(SlackAddUserToChannelProducer.name);

public static readonly QUEUE_NAME = 'SlackAddUserToChannelProducer';

public static readonly ATTEMPTS = 3;

public static readonly BACKOFF = 3;

public static readonly DELAY = 0;

public static readonly REMOVE_ON_COMPLETE = true;

public static readonly REMOVE_ON_FAIL = true;

public static readonly PRIORITY = 1;

constructor(
@InjectQueue(SlackAddUserToChannelProducer.QUEUE_NAME)
private readonly queue: Queue
) {}

// Job Options https://docs.nestjs.com/techniques/queues#job-options
async add(data: AddUserMainChannelType): Promise<Job<AddUserMainChannelType>> {
const job = await this.queue.add(data);

this.logger.verbose(
`Add user into mainchannel with email "${data.email}" to queue with Job id: "${job.id}"`
);

return job;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { SLACK_MASTER_CHANNEL_ID } from 'src/libs/constants/slack';
import {
AddUserMainChannelType,
BoardType,
ChangeResponsibleType,
MergeBoardType
Expand All @@ -10,6 +11,7 @@ import { SlackCommunicationProducer } from 'src/modules/communication/producers/
import { SlackMergeBoardProducer } from 'src/modules/communication/producers/slack-merge-board.producer';
import { SlackResponsibleProducer } from 'src/modules/communication/producers/slack-responsible.producer';
import { CommunicationServiceInterface } from '../interfaces/slack-communication.service.interface';
import { SlackAddUserToChannelProducer } from '../producers/slack-add-user-channel.producer';

@Injectable()
export class SlackCommunicationService implements CommunicationServiceInterface {
Expand All @@ -20,7 +22,9 @@ export class SlackCommunicationService implements CommunicationServiceInterface
@Inject(SlackResponsibleProducer)
private readonly slackResponsibleProducer: SlackResponsibleProducer,
@Inject(SlackMergeBoardProducer)
private readonly slackMergeBoardProducer: SlackMergeBoardProducer
private readonly slackMergeBoardProducer: SlackMergeBoardProducer,
@Inject(SlackAddUserToChannelProducer)
private readonly slackAddUserToChannelProducer: SlackAddUserToChannelProducer
) {}

public async execute(board: BoardType): Promise<void> {
Expand All @@ -35,4 +39,8 @@ export class SlackCommunicationService implements CommunicationServiceInterface
public async executeMergeBoardNotification(mergeBoard: MergeBoardType): Promise<void> {
this.slackMergeBoardProducer.add(mergeBoard);
}

public async executeAddUserMainChannel(user: AddUserMainChannelType): Promise<void> {
this.slackAddUserToChannelProducer.add(user);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Injectable, Logger } from '@nestjs/common';
import { ChangeResponsibleType, MergeBoardType } from 'src/modules/communication/dto/types';
import {
AddUserMainChannelType,
ChangeResponsibleType,
MergeBoardType
} from 'src/modules/communication/dto/types';
import { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface';

@Injectable()
Expand All @@ -24,6 +28,14 @@ export class SlackDisabledCommunicationService implements CommunicationServiceIn
);
}

public async executeAddUserMainChannel(user: AddUserMainChannelType): Promise<void> {
this.logger.warn(
`Call "executeAddUserMainChannel" method with SLACK_ENABLE "false" with: "${JSON.stringify(
user.email
)}"`
);
}

public async execute(...args: any): Promise<void> {
this.logger.warn(
`Call execute method with SLACK_ENABLE "false". Arguments "${JSON.stringify(args)}"`
Expand Down
4 changes: 2 additions & 2 deletions backend/src/modules/teams/services/get.team.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export default class GetTeamService implements GetTeamServiceInterface {
const userA = a.user as User;
const userB = b.user as User;

const fullNameA = `${userA.firstName.toLowerCase()} ${userA.lastName.toLowerCase()}`;
const fullNameB = `${userB.firstName.toLowerCase()} ${userB.lastName.toLowerCase()}`;
const fullNameA = `${userA?.firstName.toLowerCase()} ${userA?.lastName.toLowerCase()}`;
const fullNameB = `${userB?.firstName.toLowerCase()} ${userB?.lastName.toLowerCase()}`;

return fullNameA < fullNameB ? -1 : 1;
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Board/DragDropArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ const DragDropArea: React.FC<Props> = ({
socketId={socketId}
title={column.title}
userId={userId}
boardUser={board.users.find((boardUser) => boardUser.user._id === userId)}
boardUser={board.users.find((boardUser) => boardUser.user?._id === userId)}
isRegularBoard={isRegularBoard}
hasAdminRole={hasAdminRole}
addCards={board.addCards}
Expand Down
Loading

0 comments on commit e2a5232

Please sign in to comment.