Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: delete organization and wallet #757

Merged
merged 9 commits into from
Jun 10, 2024
4 changes: 2 additions & 2 deletions apps/agent-provisioning/AFJ/scripts/start_agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,14 @@ cat <<EOF >${CONFIG_FILE}
"inboundTransport": [
{
"transport": "$PROTOCOL",
"port": "$INBOUND_PORT"
"port": $INBOUND_PORT
}
],
"outboundTransport": [
"$PROTOCOL"
],
"webhookUrl": "$WEBHOOK_HOST/wh/$AGENCY",
"adminPort": "$ADMIN_PORT",
"adminPort": $ADMIN_PORT,
"tenancy": $TENANT,
"schemaFileServerURL": "$SCHEMA_FILE_SERVER_URL"
}
Expand Down
4 changes: 2 additions & 2 deletions apps/agent-service/src/agent-service.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ export class AgentServiceController {
}

@MessagePattern({ cmd: 'delete-wallet' })
async deleteWallet(payload: { url, apiKey }): Promise<object> {
return this.agentServiceService.deleteWallet(payload.url, payload.apiKey);
async deleteWallet(payload: { orgId }): Promise<object> {
return this.agentServiceService.deleteWallet(payload.orgId);
}

@MessagePattern({ cmd: 'agent-receive-invitation-url' })
Expand Down
49 changes: 42 additions & 7 deletions apps/agent-service/src/agent-service.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class AgentServiceService {
this.agentServiceRepository.getPlatformConfigDetails(),
this.agentServiceRepository.getAgentTypeDetails(),
this.agentServiceRepository.getLedgerDetails(
agentSpinupDto.network ? agentSpinupDto.network : [Ledgers.Indicio_Demonet]
agentSpinupDto.ledgerName ? agentSpinupDto.ledgerName : [Ledgers.Indicio_Demonet]
)
]);

Expand Down Expand Up @@ -1632,18 +1632,53 @@ export class AgentServiceService {
}
}

async deleteWallet(url: string, apiKey: string): Promise<object> {
async deleteWallet(orgId: string): Promise<object> {
try {
const deleteWallet = await this.commonService
.httpDelete(url, { headers: { authorization: apiKey } })
// Retrieve the API key and agent information
const [getApiKeyResult, orgAgentResult] = await Promise.allSettled([
this.getOrgAgentApiKey(orgId),
this.agentServiceRepository.getAgentApiKey(orgId)
]);

if (getApiKeyResult.status === 'rejected') {
throw new InternalServerErrorException(`Failed to get API key: ${getApiKeyResult.reason}`);
}

if (orgAgentResult.status === 'rejected') {
throw new InternalServerErrorException(`Failed to get agent information: ${orgAgentResult.reason}`);
}

const getApiKey = getApiKeyResult?.value;
const orgAgent = orgAgentResult?.value;

const orgAgentTypeResult = await this.agentServiceRepository.getOrgAgentType(orgAgent.orgAgentTypeId);

if (!orgAgentTypeResult) {
throw new NotFoundException(ResponseMessages.agent.error.orgAgentNotFound);
}

// Determine the URL based on the agent type
const url = orgAgentTypeResult.agent === OrgAgentType.SHARED
? `${orgAgent.agentEndPoint}${CommonConstants.URL_SHAGENT_DELETE_SUB_WALLET}`.replace('#', orgAgent?.tenantId)
: `${orgAgent.agentEndPoint}${CommonConstants.URL_DELETE_WALLET}`;

// Make the HTTP DELETE request
const deleteWallet = await this.commonService.httpDelete(url, {
headers: { authorization: getApiKey }
})
.then(async (response) => response);
return deleteWallet;

if (deleteWallet.status === 204) {
const deleteOrgAgent = await this.agentServiceRepository.deleteOrgAgentByOrg(orgId);
return deleteOrgAgent;
}
} catch (error) {
this.logger.error(`Error in delete wallet in agent service : ${JSON.stringify(error)}`);
throw new RpcException(error);
this.logger.error(`Error in delete wallet in agent service: ${JSON.stringify(error.message)}`);
throw new RpcException(error.response ? error.response : error);
}
}


async receiveInvitationUrl(receiveInvitationUrl: IReceiveInvitationUrl, url: string, orgId: string): Promise<string> {
try {
const getApiKey = await this.getOrgAgentApiKey(orgId);
Expand Down
22 changes: 22 additions & 0 deletions apps/agent-service/src/repositories/agent-service.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,4 +484,26 @@ export class AgentServiceRepository {
throw error;
}
}

// eslint-disable-next-line camelcase
async deleteOrgAgentByOrg(orgId: string): Promise<org_agents> {
try {
return await this.prisma.$transaction(async (prisma) => {
// Concurrently delete related records
await Promise.all([
prisma.org_dids.deleteMany({ where: { orgId } }),
prisma.agent_invitations.deleteMany({ where: { orgId } })
]);

// Delete the organization agent
const deleteOrgAgent = await prisma.org_agents.delete({ where: { orgId } });

return deleteOrgAgent;
});
} catch (error) {
this.logger.error(`[deleteOrgAgentByOrg] - Error deleting org agent record: ${error.message}`);
throw error;
}
}

}
6 changes: 6 additions & 0 deletions apps/api-gateway/common/exception-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export class CustomExceptionFilter extends BaseExceptionFilter {
exceptionResponse = exception as unknown as ExceptionResponse;
}

if (exception?.['error']) {
exceptionResponse = exception?.['error'];
} else {
exceptionResponse = exception as unknown as ExceptionResponse;
}

errorResponse = {
statusCode: exceptionResponse.statusCode ? exceptionResponse.statusCode : status,
message: exceptionResponse.message
Expand Down
28 changes: 26 additions & 2 deletions apps/api-gateway/src/agent-service/agent-service.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
Res,
Get,
UseFilters,
Param
Param,
Delete
} from '@nestjs/common';
import {
ApiTags,
Expand Down Expand Up @@ -277,7 +278,7 @@ export class AgentController {
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@Roles(OrgRoles.OWNER, OrgRoles.ADMIN)
@ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto })
async agentconfigure(
async agentConfigure(
@Param('orgId') orgId: string,
@Body() agentConfigureDto: AgentConfigureDto,
@User() user: user,
Expand All @@ -296,4 +297,27 @@ export class AgentController {

return res.status(HttpStatus.CREATED).json(finalResponse);
}

@Delete('/orgs/:orgId/agents/wallet')
@ApiOperation({
summary: 'Delete wallet',
description: 'Delete agent wallet by organization.'
})
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@Roles(OrgRoles.OWNER)
@ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto })
async deleteWallet(
@Param('orgId') orgId: string,
@User() user: user,
@Res() res: Response
): Promise<Response> {
await this.agentService.deleteWallet(orgId, user);

const finalResponse: IResponseType = {
statusCode: HttpStatus.OK,
message: ResponseMessages.agent.success.walletDelete
};

return res.status(HttpStatus.OK).json(finalResponse);
}
}
7 changes: 7 additions & 0 deletions apps/api-gateway/src/agent-service/agent-service.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,11 @@ export class AgentService extends BaseService {
return this.sendNatsMessage(this.agentServiceProxy, 'agent-configure', payload);
}

async deleteWallet(orgId: string, user: user): Promise<object> {
const payload = { orgId, user };
// NATS call

return this.sendNatsMessage(this.agentServiceProxy, 'delete-wallet', payload);
}

}
9 changes: 4 additions & 5 deletions apps/api-gateway/src/organization/organization.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,15 +516,14 @@ export class OrganizationController {
}

/**
* @returns Boolean
* @returns Organization
*/
//Todo
@Delete('/:orgId')
@ApiOperation({ summary: 'Delete Organization', description: 'Delete an organization' })
@ApiExcludeEndpoint()
@ApiResponse({ status: HttpStatus.ACCEPTED, description: 'Success', type: ApiResponseDto })
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'))
@Roles(OrgRoles.OWNER)
async deleteOrganization(
@Param('orgId') orgId: string,
@Res() res: Response
Expand All @@ -533,10 +532,10 @@ export class OrganizationController {
await this.organizationService.deleteOrganization(orgId);

const finalResponse: IResponse = {
statusCode: HttpStatus.ACCEPTED,
statusCode: HttpStatus.OK,
message: ResponseMessages.organisation.success.delete
};
return res.status(HttpStatus.ACCEPTED).json(finalResponse);
return res.status(HttpStatus.OK).json(finalResponse);
}

@Delete('/:orgId/client_credentials')
Expand Down
2 changes: 1 addition & 1 deletion apps/api-gateway/src/organization/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export class OrganizationService extends BaseService {

async deleteOrganization(
orgId: string
): Promise<boolean> {
): Promise<organisation> {
const payload = { orgId };

return this.sendNatsMessage(this.serviceProxy, 'delete-organization', payload);
Expand Down
19 changes: 18 additions & 1 deletion apps/organization/interfaces/organization.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,21 @@ export interface IDidDetails {
export interface IPrimaryDidDetails extends IPrimaryDid {
id: string,
didDocument: Prisma.JsonValue
}
}

export interface IDeleteOrganization {
id: string;
createDateTime: Date;
createdBy: string;
lastChangedDateTime: Date;
lastChangedBy: string;
name: string;
description: string;
orgSlug: string;
logoUrl: string;
website: string;
publicProfile: boolean;
idpId: string;
clientId: string;
clientSecret: string;
}
78 changes: 56 additions & 22 deletions apps/organization/repositories/organization.repository.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable prefer-destructuring */
/* eslint-disable camelcase */

import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { ConflictException, Injectable, Logger, NotFoundException } from '@nestjs/common';
// eslint-disable-next-line camelcase
import { Prisma, agent_invitations, org_agents, org_invitations, user_org_roles } from '@prisma/client';

import { CreateOrganizationDto } from '../dtos/create-organization.dto';
import { IDidDetails, IDidList, IGetOrgById, IGetOrganization, IPrimaryDidDetails, IUpdateOrganization } from '../interfaces/organization.interface';
import { IDeleteOrganization, IDidDetails, IDidList, IGetOrgById, IGetOrganization, IPrimaryDidDetails, IUpdateOrganization } from '../interfaces/organization.interface';
import { InternalServerErrorException } from '@nestjs/common';
import { Invitation, SortValue } from '@credebl/enum/enum';
import { PrismaService } from '@credebl/prisma-service';
Expand Down Expand Up @@ -724,28 +724,62 @@ export class OrganizationRepository {
}
}

async deleteOrg(id: string): Promise<boolean> {
async deleteOrg(id: string): Promise<IDeleteOrganization> {
const tablesToCheck = [
'org_invitations',
'org_agents',
'org_dids',
'agent_invitations',
'connections',
'credentials',
'presentations',
'ecosystem_invitations',
'ecosystem_orgs',
'file_upload',
'notification'
];

try {
await Promise.all([
this.prisma.user_activity.deleteMany({ where: { orgId: id } }),
this.prisma.user_org_roles.deleteMany({ where: { orgId: id } }),
this.prisma.org_invitations.deleteMany({ where: { orgId: id } }),
this.prisma.schema.deleteMany({ where: { orgId: id } }),
this.prisma.credential_definition.deleteMany({ where: { orgId: id } }),
this.prisma.agent_invitations.deleteMany({ where: { orgId: id } }),
this.prisma.org_agents.deleteMany({ where: { orgId: id } }),
this.prisma.connections.deleteMany({ where: { orgId: id } }),
this.prisma.credentials.deleteMany({ where: { orgId: id } }),
this.prisma.presentations.deleteMany({ where: { orgId: id } }),
this.prisma.ecosystem_invitations.deleteMany({ where: { orgId: `${id}` } }),
this.prisma.file_upload.deleteMany({ where: { orgId: `${id}` } }),
this.prisma.ecosystem_orgs.deleteMany({ where: { orgId: `${id}` } }),
this.prisma.organisation.deleteMany({ where: { id } })
]);
return true;
return await this.prisma.$transaction(async (prisma) => {
// Check for references in all tables in parallel
const referenceCounts = await Promise.all(
tablesToCheck.map(table => prisma[table].count({ where: { orgId: id } }))
);

referenceCounts.forEach((count, index) => {
if (0 < count) {
throw new ConflictException(`Organization ID ${id} is referenced in the table ${tablesToCheck[index]}`);
}
});

// Check if the organization is an ecosystem lead
const isEcosystemLead = await prisma.ecosystem_orgs.findMany({
where: {
orgId: id,
ecosystemRole: {
name: { in: ['Ecosystem Lead', 'Ecosystem Owner'] }
}
}
});

if (0 < isEcosystemLead.length) {
throw new ConflictException(ResponseMessages.organisation.error.organizationEcosystemValidate);
}

// User activity delete by orgId
await prisma.user_activity.deleteMany({ where: { orgId: id } });

// User org role delete by orgId
await prisma.user_org_roles.deleteMany({ where: { orgId: id } });

// If no references are found, delete the organization
const deleteOrg = await prisma.organisation.delete({ where: { id } });

return deleteOrg;
});
} catch (error) {
this.logger.error(`error: ${JSON.stringify(error)}`);
throw error;
this.logger.error(`Error in deleteOrg: ${error}`);
throw error;
}
}

Expand Down
4 changes: 2 additions & 2 deletions apps/organization/src/organization.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Body } from '@nestjs/common';
import { CreateOrganizationDto } from '../dtos/create-organization.dto';
import { BulkSendInvitationDto } from '../dtos/send-invitation.dto';
import { UpdateInvitationDto } from '../dtos/update-invitation.dt';
import { IDidList, IGetOrgById, IGetOrganization, IUpdateOrganization, Payload } from '../interfaces/organization.interface';
import { IDeleteOrganization, IDidList, IGetOrgById, IGetOrganization, IUpdateOrganization, Payload } from '../interfaces/organization.interface';
import { organisation } from '@prisma/client';
import { IOrgCredentials, IOrganizationInvitations, IOrganization, IOrganizationDashboard } from '@credebl/common/interfaces/organization.interface';
import { IAccessTokenData } from '@credebl/common/interfaces/interface';
Expand Down Expand Up @@ -227,7 +227,7 @@ export class OrganizationController {
}

@MessagePattern({ cmd: 'delete-organization' })
async deleteOrganization(payload: { orgId: string }): Promise<boolean> {
async deleteOrganization(payload: { orgId: string }): Promise<IDeleteOrganization> {
return this.organizationService.deleteOrganization(payload.orgId);
}

Expand Down
Loading