140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
Headers,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Query,
|
|
Req,
|
|
UnauthorizedException,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import type { Request } from 'express';
|
|
import { JwtAuthGuard } from '../../auth/jwt-auth.guard';
|
|
import { CurrentUser } from '../../auth/current-user.decorator';
|
|
import { Roles } from '../../auth/roles.decorator';
|
|
import { RolesGuard } from '../../auth/roles.guard';
|
|
import { OrgModuleGuard } from '../../billing/org-module.guard';
|
|
import { RequireOrgModule } from '../../billing/org-module.decorator';
|
|
import { PrismaService } from '../../prisma/prisma.service';
|
|
import type { VoipWebhookEvent } from '@mento/shared';
|
|
import { VoipConnectionsService } from './connections.service';
|
|
import { VoipCallsService } from './calls.service';
|
|
import {
|
|
CreateVoipConnectionDto,
|
|
UpdateVoipConnectionDto,
|
|
VoipClickToCallDto,
|
|
} from './dto';
|
|
|
|
@Controller('integrations/voip')
|
|
@UseGuards(JwtAuthGuard, RolesGuard, OrgModuleGuard)
|
|
@RequireOrgModule('voip')
|
|
export class VoipController {
|
|
constructor(
|
|
private readonly connections: VoipConnectionsService,
|
|
private readonly calls: VoipCallsService,
|
|
) {}
|
|
|
|
@Get('connections')
|
|
@Roles('owner', 'admin')
|
|
list(@CurrentUser() user: { organizationId: string }) {
|
|
return this.connections.list(user.organizationId);
|
|
}
|
|
|
|
@Post('connections')
|
|
@Roles('owner', 'admin')
|
|
create(
|
|
@CurrentUser() user: { organizationId: string },
|
|
@Body() dto: CreateVoipConnectionDto,
|
|
) {
|
|
return this.connections.create(user.organizationId, dto);
|
|
}
|
|
|
|
@Patch('connections/:id')
|
|
@Roles('owner', 'admin')
|
|
update(
|
|
@CurrentUser() user: { organizationId: string },
|
|
@Param('id') id: string,
|
|
@Body() dto: UpdateVoipConnectionDto,
|
|
) {
|
|
return this.connections.update(user.organizationId, id, dto);
|
|
}
|
|
|
|
@Delete('connections/:id')
|
|
@Roles('owner', 'admin')
|
|
async remove(
|
|
@CurrentUser() user: { organizationId: string },
|
|
@Param('id') id: string,
|
|
) {
|
|
await this.connections.remove(user.organizationId, id);
|
|
return { ok: true };
|
|
}
|
|
|
|
@Get('calls')
|
|
@Roles('owner', 'admin')
|
|
recent(
|
|
@CurrentUser() user: { organizationId: string },
|
|
@Query('connectionId') connectionId?: string,
|
|
) {
|
|
return this.calls.listRecent(user.organizationId, connectionId);
|
|
}
|
|
|
|
@Post('click-to-call')
|
|
clickToCall(
|
|
@CurrentUser() user: { organizationId: string; userId: string },
|
|
@Body() dto: VoipClickToCallDto,
|
|
) {
|
|
return this.calls.clickToCall(
|
|
user.organizationId,
|
|
user.userId,
|
|
dto.number,
|
|
dto.personId,
|
|
);
|
|
}
|
|
}
|
|
|
|
@Controller('public/integrations/voip')
|
|
export class VoipWebhookController {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly connections: VoipConnectionsService,
|
|
private readonly calls: VoipCallsService,
|
|
) {}
|
|
|
|
@Post(':connectionId/webhook')
|
|
async webhook(
|
|
@Param('connectionId') connectionId: string,
|
|
@Headers('x-voip-signature') signature: string | undefined,
|
|
@Headers('x-montact-signature') montactSignature: string | undefined,
|
|
@Req() req: Request & { rawBody?: Buffer },
|
|
@Body() body: VoipWebhookEvent,
|
|
) {
|
|
const row = await this.prisma.voipConnection.findFirst({
|
|
where: { id: connectionId, enabled: true },
|
|
});
|
|
if (!row) throw new UnauthorizedException('اتصال نامعتبر است');
|
|
|
|
const secret = await this.connections.decryptWebhookSecret(row);
|
|
if (secret) {
|
|
const raw =
|
|
req.rawBody ?? Buffer.from(JSON.stringify(body ?? {}), 'utf8');
|
|
const ok = this.connections.verifySignature(
|
|
raw,
|
|
signature ?? montactSignature,
|
|
secret,
|
|
);
|
|
if (!ok) throw new UnauthorizedException('امضای وبهوک نامعتبر است');
|
|
}
|
|
|
|
if (!body?.callId) {
|
|
return { ok: false, error: 'callId required' };
|
|
}
|
|
|
|
const result = await this.calls.ingestWebhook(connectionId, body);
|
|
return { ok: true, ...result };
|
|
}
|
|
}
|