2026-09-26 16:12:29 +03:30
|
|
|
import {
|
|
|
|
|
Controller,
|
|
|
|
|
Get,
|
|
|
|
|
Param,
|
|
|
|
|
Query,
|
|
|
|
|
UseGuards,
|
|
|
|
|
} from '@nestjs/common';
|
|
|
|
|
import { IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
|
|
|
|
|
import { Type } from 'class-transformer';
|
|
|
|
|
import { CurrentUser } from '../auth/current-user.decorator';
|
2026-10-06 15:04:46 +03:30
|
|
|
import type { Prisma } from '@prisma/client';
|
2026-09-26 16:12:29 +03:30
|
|
|
import { PrismaService } from '../prisma/prisma.service';
|
2026-10-06 15:04:46 +03:30
|
|
|
import { buildSearchWhere, ci } from '../common/search';
|
2026-09-26 16:12:29 +03:30
|
|
|
import { ApiKeyAuthGuard } from './api-key-auth.guard';
|
|
|
|
|
|
|
|
|
|
class ListQueryDto {
|
|
|
|
|
@IsOptional()
|
|
|
|
|
@IsString()
|
|
|
|
|
q?: string;
|
|
|
|
|
|
|
|
|
|
@IsOptional()
|
|
|
|
|
@Type(() => Number)
|
|
|
|
|
@IsInt()
|
|
|
|
|
@Min(1)
|
|
|
|
|
page?: number = 1;
|
|
|
|
|
|
|
|
|
|
@IsOptional()
|
|
|
|
|
@Type(() => Number)
|
|
|
|
|
@IsInt()
|
|
|
|
|
@Min(1)
|
|
|
|
|
@Max(100)
|
|
|
|
|
limit?: number = 20;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Controller('public/v1')
|
|
|
|
|
@UseGuards(ApiKeyAuthGuard)
|
|
|
|
|
export class PublicApiV1Controller {
|
|
|
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
|
|
|
|
|
|
@Get('people')
|
|
|
|
|
async listPeople(
|
|
|
|
|
@CurrentUser() user: { organizationId: string },
|
|
|
|
|
@Query() query: ListQueryDto,
|
|
|
|
|
) {
|
|
|
|
|
const page = query.page ?? 1;
|
|
|
|
|
const limit = query.limit ?? 20;
|
2026-10-06 15:04:46 +03:30
|
|
|
const search = buildSearchWhere<Prisma.PersonWhereInput>(query.q, (t) => [
|
|
|
|
|
{ firstName: ci(t) },
|
|
|
|
|
{ lastName: ci(t) },
|
|
|
|
|
{ mobile: { contains: t } },
|
|
|
|
|
{ phone: { contains: t } },
|
|
|
|
|
{ email: ci(t) },
|
|
|
|
|
]);
|
|
|
|
|
const where: Prisma.PersonWhereInput = {
|
2026-09-26 16:12:29 +03:30
|
|
|
organizationId: user.organizationId,
|
2026-10-06 15:04:46 +03:30
|
|
|
...(search ? { AND: [search] } : {}),
|
2026-09-26 16:12:29 +03:30
|
|
|
};
|
|
|
|
|
const [total, items] = await Promise.all([
|
|
|
|
|
this.prisma.person.count({ where }),
|
|
|
|
|
this.prisma.person.findMany({
|
|
|
|
|
where,
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
email: true,
|
|
|
|
|
mobile: true,
|
|
|
|
|
phone: true,
|
|
|
|
|
createdAt: true,
|
|
|
|
|
updatedAt: true,
|
|
|
|
|
},
|
|
|
|
|
orderBy: { updatedAt: 'desc' },
|
|
|
|
|
skip: (page - 1) * limit,
|
|
|
|
|
take: limit,
|
|
|
|
|
}),
|
|
|
|
|
]);
|
|
|
|
|
return {
|
|
|
|
|
total,
|
|
|
|
|
page,
|
|
|
|
|
limit,
|
|
|
|
|
items: items.map((p) => ({
|
|
|
|
|
...p,
|
|
|
|
|
fullName: `${p.firstName} ${p.lastName}`.trim(),
|
|
|
|
|
createdAt: p.createdAt.toISOString(),
|
|
|
|
|
updatedAt: p.updatedAt.toISOString(),
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get('people/:id')
|
|
|
|
|
async getPerson(
|
|
|
|
|
@CurrentUser() user: { organizationId: string },
|
|
|
|
|
@Param('id') id: string,
|
|
|
|
|
) {
|
|
|
|
|
const p = await this.prisma.person.findFirst({
|
|
|
|
|
where: { id, organizationId: user.organizationId },
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
email: true,
|
|
|
|
|
mobile: true,
|
|
|
|
|
phone: true,
|
|
|
|
|
notes: true,
|
|
|
|
|
tags: true,
|
|
|
|
|
createdAt: true,
|
|
|
|
|
updatedAt: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
if (!p) return null;
|
|
|
|
|
return {
|
|
|
|
|
...p,
|
|
|
|
|
fullName: `${p.firstName} ${p.lastName}`.trim(),
|
|
|
|
|
createdAt: p.createdAt.toISOString(),
|
|
|
|
|
updatedAt: p.updatedAt.toISOString(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get('deals')
|
|
|
|
|
async listDeals(
|
|
|
|
|
@CurrentUser() user: { organizationId: string },
|
|
|
|
|
@Query() query: ListQueryDto,
|
|
|
|
|
) {
|
|
|
|
|
const page = query.page ?? 1;
|
|
|
|
|
const limit = query.limit ?? 20;
|
2026-10-06 15:04:46 +03:30
|
|
|
const search = buildSearchWhere<Prisma.DealWhereInput>(query.q, (t) => [{ title: ci(t) }]);
|
|
|
|
|
const where: Prisma.DealWhereInput = {
|
2026-09-26 16:12:29 +03:30
|
|
|
organizationId: user.organizationId,
|
2026-10-06 15:04:46 +03:30
|
|
|
...(search ? { AND: [search] } : {}),
|
2026-09-26 16:12:29 +03:30
|
|
|
};
|
|
|
|
|
const [total, items] = await Promise.all([
|
|
|
|
|
this.prisma.deal.count({ where }),
|
|
|
|
|
this.prisma.deal.findMany({
|
|
|
|
|
where,
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
title: true,
|
|
|
|
|
status: true,
|
|
|
|
|
amount: true,
|
|
|
|
|
currency: true,
|
|
|
|
|
source: true,
|
|
|
|
|
stageId: true,
|
|
|
|
|
pipelineId: true,
|
|
|
|
|
createdAt: true,
|
|
|
|
|
updatedAt: true,
|
|
|
|
|
},
|
|
|
|
|
orderBy: { updatedAt: 'desc' },
|
|
|
|
|
skip: (page - 1) * limit,
|
|
|
|
|
take: limit,
|
|
|
|
|
}),
|
|
|
|
|
]);
|
|
|
|
|
return {
|
|
|
|
|
total,
|
|
|
|
|
page,
|
|
|
|
|
limit,
|
|
|
|
|
items: items.map((d) => ({
|
|
|
|
|
...d,
|
|
|
|
|
amount: d.amount == null ? null : Number(d.amount),
|
|
|
|
|
createdAt: d.createdAt.toISOString(),
|
|
|
|
|
updatedAt: d.updatedAt.toISOString(),
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|