Skip to content

Commit

Permalink
Added APIs to delete bots. (#224)
Browse files Browse the repository at this point in the history
  • Loading branch information
chinmoy12c authored Sep 15, 2023
1 parent 74c45ff commit cdd400d
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 20 deletions.
18 changes: 15 additions & 3 deletions src/modules/bot/bot.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { diskStorage } from 'multer';
import { Request } from 'express';
import { extname } from 'path';
import fs from 'fs';
import { DeleteBotsDTO } from './dto/delete-bot-dto';


const editFileName = (req: Request, file: Express.Multer.File, callback) => {
Expand Down Expand Up @@ -335,15 +336,26 @@ export class BotController {
return this.botService.update(id, updateBotDto);
}

@Delete(':id')
@Delete()
@UseInterceptors(
AddResponseObjectInterceptor,
AddAdminHeaderInterceptor,
AddOwnerInfoInterceptor,
AddROToResponseInterceptor,
)
remove(@Param('id') id: string) {
return this.botService.remove(id);
async remove(@Body() body: DeleteBotsDTO) {
return await this.botService.remove(body);
}

@Delete(':botId')
@UseInterceptors(
AddResponseObjectInterceptor,
AddAdminHeaderInterceptor,
AddOwnerInfoInterceptor,
AddROToResponseInterceptor,
)
async removeOne(@Param('botId') botId: string) {
return await this.botService.removeOne(botId);
}

@Get(':botId/broadcastReport')
Expand Down
125 changes: 122 additions & 3 deletions src/modules/bot/bot.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,31 @@ const MockPrismaService = {
}
},
count: () => 10,
update: jest.fn()
}
update: jest.fn(),
deleteMany: (filter) => {
deletedIds.push({'bot': filter.where.id.in});
}
},
service: {
deleteMany: (filter) => {
deletedIds.push({'service': filter.where.id.in});
}
},
userSegment: {
deleteMany: (filter) => {
deletedIds.push({'userSegment': filter.where.id.in});
}
},
transformerConfig: {
deleteMany: (filter) => {
deletedIds.push({'transformerConfig': filter.where.id.in});
}
},
conversationLogic: {
deleteMany: (filter) => {
deletedIds.push({'conversationLogic': filter.where.id.in});
}
},
}

class MockConfigService {
Expand Down Expand Up @@ -320,6 +343,9 @@ const mockConfig = {
"totalRecords": 1
};

// Used for delete bot testing
let deletedIds: any[] = []

describe('BotService', () => {
let botService: BotService;
let configService: ConfigService;
Expand Down Expand Up @@ -506,7 +532,7 @@ describe('BotService', () => {
});

it('bot update throws NotFoundException when non existent bot is updated',async () => {
fetchMock.getOnce(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
fetchMock.deleteOnce(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
true
);
expect(botService.update('testBotIdNotExisting', {
Expand Down Expand Up @@ -605,4 +631,97 @@ describe('BotService', () => {
).toBe(true);
fetchMock.restore();
});

it('bot delete with bot id list works as expected', async () => {
fetchMock.delete(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
true
);
mockBotsDb[0].status = BotStatus.DISABLED;
await botService.remove({ids: ['testId'], endDate: null});
expect(deletedIds).toEqual(
[
{'service': ['testId']},
{'userSegment': ['testUserId']},
{'transformerConfig': ['testTransformerId']},
{'conversationLogic': ['testLogicId']},
{'bot': ['testId']},
]
);
deletedIds = [];
await botService.remove({ids: ['nonExisting'], endDate: null});
expect(deletedIds).toEqual(
[
{'service': []},
{'userSegment': []},
{'transformerConfig': []},
{'conversationLogic': []},
{'bot': []},
]
);
deletedIds = [];
expect(fetchMock.called(
`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`
))
.toBe(true);
mockBotsDb[0].status = BotStatus.ENABLED;
fetchMock.restore();
});

it('bot delete with endDate works as expected', async () => {
fetchMock.delete(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
true
);
mockBotsDb[0].status = BotStatus.DISABLED;
await botService.remove({ids: null, endDate: '2025-12-01'});
expect(deletedIds).toEqual(
[
{'service': ['testId']},
{'userSegment': ['testUserId']},
{'transformerConfig': ['testTransformerId']},
{'conversationLogic': ['testLogicId']},
{'bot': ['testId']},
]
);
deletedIds = [];
await botService.remove({ids: null, endDate: '2023-12-01'});
expect(deletedIds).toEqual(
[
{'service': []},
{'userSegment': []},
{'transformerConfig': []},
{'conversationLogic': []},
{'bot': []},
]
);
expect(fetchMock.called(
`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`
))
.toBe(true);
deletedIds = [];
mockBotsDb[0].status = BotStatus.ENABLED;
fetchMock.restore();
});

it('bot delete only deletes disabled bots', async () => {
fetchMock.delete(`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`,
true
);
mockBotsDb[0].status = BotStatus.ENABLED;
await botService.remove({ids: ['testId'], endDate: null});
expect(deletedIds).toEqual(
[
{'service': []},
{'userSegment': []},
{'transformerConfig': []},
{'conversationLogic': []},
{'bot': []},
]
);
expect(fetchMock.called(
`${configService.get<string>('UCI_CORE_BASE_URL')}${configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT')}`
))
.toBe(true);
deletedIds = [];
fetchMock.restore();
});
});
131 changes: 117 additions & 14 deletions src/modules/bot/bot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const limit = pLimit(1);
import fs from 'fs';
import FormData from 'form-data';
import { Cache } from 'cache-manager';
import { DeleteBotsDTO } from './dto/delete-bot-dto';

@Injectable()
export class BotService {
Expand Down Expand Up @@ -237,7 +238,7 @@ export class BotService {
}
}

async findAllUnresolved(): Promise<Promise<Prisma.BotGetPayload<{
async findAllUnresolved(): Promise<Prisma.BotGetPayload<{
include: {
users: {
include: {
Expand All @@ -251,7 +252,7 @@ export class BotService {
};
};
};
}>[] | null>> {
}>[]> {
const startTime = performance.now();
const cacheKey = `unresolved_bots_data`;
const cachedBots = await this.cacheManager.get(cacheKey);
Expand Down Expand Up @@ -503,14 +504,6 @@ export class BotService {
}

async update(id: string, updateBotDto: any) {
const inbound_base = this.configService.get<string>('UCI_CORE_BASE_URL');
const caffine_invalidate_endpoint = this.configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT');
const transaction_layer_auth_token = this.configService.get<string>('AUTHORIZATION_KEY_TRANSACTION_LAYER');
if (!inbound_base || !caffine_invalidate_endpoint || !transaction_layer_auth_token) {
this.logger.error(`Missing configuration: inbound endpoint: ${inbound_base}, caffine endpoint: ${caffine_invalidate_endpoint} or transaction layer auth token.`);
throw new InternalServerErrorException();
}
const caffine_reset_url = `${inbound_base}${caffine_invalidate_endpoint}`;
const existingBot = await this.findOne(id);
if (!existingBot) {
throw new NotFoundException("Bot does not exist!")
Expand Down Expand Up @@ -547,7 +540,20 @@ export class BotService {
data: updateBotDto,
});
await this.cacheManager.reset();
await fetch(caffine_reset_url, {method: 'DELETE', headers: {'Authorization': transaction_layer_auth_token}})
await this.invalidateTransactionLayerCache();
return updatedBot;
}

async invalidateTransactionLayerCache() {
const inbound_base = this.configService.get<string>('UCI_CORE_BASE_URL');
const caffine_invalidate_endpoint = this.configService.get<string>('CAFFINE_INVALIDATE_ENDPOINT');
const transaction_layer_auth_token = this.configService.get<string>('AUTHORIZATION_KEY_TRANSACTION_LAYER');
if (!inbound_base || !caffine_invalidate_endpoint || !transaction_layer_auth_token) {
this.logger.error(`Missing configuration: inbound endpoint: ${inbound_base}, caffine reset endpoint: ${caffine_invalidate_endpoint} or transaction layer auth token.`);
throw new InternalServerErrorException();
}
const caffine_reset_url = `${inbound_base}${caffine_invalidate_endpoint}`;
return fetch(caffine_reset_url, {method: 'DELETE', headers: {'Authorization': transaction_layer_auth_token}})
.then((resp) => {
if (resp.ok) {
return resp.json();
Expand All @@ -561,11 +567,108 @@ export class BotService {
this.logger.error(`Got failure response from inbound on cache invalidation endpoint ${caffine_reset_url}. Error: ${err}`);
throw new ServiceUnavailableException('Could not invalidate cache after update!');
});
return updatedBot;
}

remove(id: string) {
return `This action removes a #${id} adapter`;
async remove(deleteBotsDTO: DeleteBotsDTO) {
let botIds = new Set();
if (deleteBotsDTO.ids) {
botIds = new Set(deleteBotsDTO.ids);
}
const endDate = deleteBotsDTO.endDate;
if ((!botIds || botIds.size == 0) && !endDate) {
throw new BadRequestException('Bot ids or endDate need to be provided!');
}
let parsedEndDate: Date;
if (endDate) {
const dateRegex: RegExp = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(endDate)) {
throw new BadRequestException(`Bad date format! Please provide date in 'yyyy-mm-dd' format.`)
}
try {
parsedEndDate = new Date(endDate);
}
catch (err) {
throw new BadRequestException(`Invalid date! Please enter a valid date.`)
}
}

const allBots = await this.findAllUnresolved();
const requiredBotIds: string[] = [], requiredServiceIds: string[] = [],
requiredUserIds: string[] = [], requiredLogicIds: string[] = [],
requiredTransformerConfigIds: string[] = [];
allBots.forEach(bot => {
if (bot.status == BotStatus.DISABLED) {
const currentParsedEndDate = new Date(bot.endDate!);
if (
(botIds.has(bot.id) && !endDate) ||
(endDate && (parsedEndDate.getTime() >= currentParsedEndDate.getTime()) && botIds.size == 0) ||
(botIds.has(bot.id) && (endDate && (parsedEndDate.getTime() >= currentParsedEndDate.getTime())))
) {
requiredBotIds.push(bot.id);
if (bot.logicIDs.length > 0) {
requiredLogicIds.push(bot.logicIDs[0].id);
if (bot.logicIDs[0].transformers.length > 0) {
requiredTransformerConfigIds.push(bot.logicIDs[0].transformers[0].id);
}
}
if (bot.users.length > 0) {
requiredUserIds.push(bot.users[0].id);
if (bot.users[0].all != null) {
requiredServiceIds.push(bot.users[0].all.id);
}
}
}
}
});
const deletePromises = [
this.prisma.service.deleteMany({
where: {
id: {
in: requiredServiceIds,
}
}
}),
this.prisma.userSegment.deleteMany({
where: {
id: {
in: requiredUserIds,
}
}
}),
this.prisma.transformerConfig.deleteMany({
where: {
id: {
in: requiredTransformerConfigIds,
}
}
}),
this.prisma.conversationLogic.deleteMany({
where: {
id: {
in: requiredLogicIds,
}
}
}),
this.prisma.bot.deleteMany({
where: {
id: {
in: requiredBotIds,
}
}
}),
];

return Promise.all(deletePromises)
.then(() => {
return this.invalidateTransactionLayerCache();
})
.catch((err) => {
throw err;
});
}

async removeOne(botId: string) {
return this.remove({ids: [botId], endDate: null});
}

async getBroadcastReport(botId: string, limit: number, nextPage: string) {
Expand Down
4 changes: 4 additions & 0 deletions src/modules/bot/dto/delete-bot-dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class DeleteBotsDTO {
ids: string[] | undefined | null;
endDate: string | undefined | null;
}

0 comments on commit cdd400d

Please sign in to comment.