feat(public-api): let external sites register a lead with their key
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,18 +1,55 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
|
||||
import { IsInt, IsOptional, IsString, Max, MaxLength, Min, MinLength } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { buildSearchWhere, ci } from '../common/search';
|
||||
import { LeadsService } from '../leads/leads.service';
|
||||
import { ApiKeyAuthGuard } from './api-key-auth.guard';
|
||||
|
||||
class CreateExternalLeadDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
firstName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
lastName?: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
@MaxLength(20)
|
||||
mobile!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
storeName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
source?: string;
|
||||
}
|
||||
|
||||
const LEAD_RATE_WINDOW_MS = 60_000;
|
||||
const LEAD_RATE_MAX = 60;
|
||||
const leadHits = new Map<string, number[]>();
|
||||
|
||||
class ListQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@@ -35,7 +72,36 @@ class ListQueryDto {
|
||||
@Controller('public/v1')
|
||||
@UseGuards(ApiKeyAuthGuard)
|
||||
export class PublicApiV1Controller {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly leads: LeadsService,
|
||||
) {}
|
||||
|
||||
@Post('leads')
|
||||
@HttpCode(200)
|
||||
createLead(
|
||||
@CurrentUser()
|
||||
user: { organizationId: string; apiKeyId?: string; apiKeyName?: string },
|
||||
@Body() body: CreateExternalLeadDto,
|
||||
) {
|
||||
const bucket = user.apiKeyId || user.organizationId;
|
||||
const now = Date.now();
|
||||
const prev = (leadHits.get(bucket) ?? []).filter((t) => now - t < LEAD_RATE_WINDOW_MS);
|
||||
if (prev.length >= LEAD_RATE_MAX) {
|
||||
throw new HttpException('تعداد ارسال بیش از حد مجاز است. کمی بعد دوباره تلاش کنید.', HttpStatus.TOO_MANY_REQUESTS);
|
||||
}
|
||||
prev.push(now);
|
||||
leadHits.set(bucket, prev);
|
||||
return this.leads.createFromExternal({
|
||||
organizationId: user.organizationId,
|
||||
siteName: user.apiKeyName?.trim() || 'سایت بیرونی',
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
mobile: body.mobile,
|
||||
storeName: body.storeName,
|
||||
source: body.source,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('people')
|
||||
async listPeople(
|
||||
|
||||
Reference in New Issue
Block a user