import { Body, Controller, Delete, Get, HttpCode, IsArray, IsOptional, IsString, NotFoundException, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common'; import { and, desc, eq, isNull } from 'drizzle-orm'; import { CurrentSession } from '../auth/session.decorator'; import type { AuthenticatedSession } from '../auth/tenant.guard'; import { db } from '../db/client'; import { contacts } from '../db/schema'; class CreateContactDto { @IsString() fullName!: string; @IsOptional() @IsString() email?: string; @IsOptional() @IsString() phone?: string; @IsOptional() @IsString() role?: string; @IsOptional() @IsString() organizationId?: string; @IsOptional() @IsString() linkedinUrl?: string; @IsOptional() @IsString() notes?: string; @IsOptional() @IsArray() tags?: string[]; @IsOptional() @IsString() source?: string; @IsOptional() @IsString() consentStatus?: string; } class UpdateContactDto { @IsOptional() @IsString() fullName?: string; @IsOptional() @IsString() email?: string; @IsOptional() @IsString() phone?: string; @IsOptional() @IsString() role?: string; @IsOptional() @IsString() organizationId?: string; @IsOptional() @IsString() linkedinUrl?: string; @IsOptional() @IsString() notes?: string; @IsOptional() @IsArray() tags?: string[]; @IsOptional() @IsString() consentStatus?: string; } @Controller('contacts') export class ContactsController { @Get() async list(@CurrentSession() session: AuthenticatedSession, @Query('limit') limitStr?: string) { const limit = Math.min(Math.max(1, parseInt(limitStr ?? '200', 10) || 200), 500); return db.query.contacts.findMany({ where: and(eq(contacts.tenantId, session.tenantId), isNull(contacts.deletedAt)), orderBy: desc(contacts.createdAt), limit, }); } @Post() async create(@CurrentSession() session: AuthenticatedSession, @Body() dto: CreateContactDto) { const [contact] = await db.insert(contacts).values({ ...dto, tenantId: session.tenantId, tags: dto.tags ?? [], }).returning(); return contact; } @Get(':id') async getOne(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) { const contact = await db.query.contacts.findFirst({ where: and(eq(contacts.id, id), eq(contacts.tenantId, session.tenantId), isNull(contacts.deletedAt)), }); if (!contact) throw new NotFoundException('Contact not found'); return contact; } @Patch(':id') async update(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateContactDto) { const [updated] = await db.update(contacts).set({ ...dto, updatedAt: new Date() }) .where(and(eq(contacts.id, id), eq(contacts.tenantId, session.tenantId))).returning(); if (!updated) throw new NotFoundException('Contact not found'); return updated; } @Delete(':id') @HttpCode(204) async remove(@CurrentSession() session: AuthenticatedSession, @Param('id', ParseUUIDPipe) id: string) { await db.update(contacts).set({ deletedAt: new Date() }) .where(and(eq(contacts.id, id), eq(contacts.tenantId, session.tenantId))); } }