feat: Add company linking to personal contacts

- Add companyId column to personal_contacts table
- Update personal-contact service to include companyName in list
- Add getContactsByCompanyId function for company contacts endpoint
- Add GET /companies/:companyId/contacts endpoint
- Add companyId to contact validation schema

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
richardtekula
2025-12-12 08:03:29 +01:00
parent 8656fb1db0
commit 8770a98db8
5 changed files with 50 additions and 3 deletions

View File

@@ -7,6 +7,7 @@ const normalizePayload = (body) => ({
phone: body.phone?.trim(), phone: body.phone?.trim(),
email: body.email?.trim(), email: body.email?.trim(),
secondaryEmail: body.secondaryEmail?.trim() || null, secondaryEmail: body.secondaryEmail?.trim() || null,
companyId: body.companyId || null,
}) })
export const listPersonalContacts = async (req, res, next) => { export const listPersonalContacts = async (req, res, next) => {
@@ -57,3 +58,13 @@ export const deletePersonalContact = async (req, res, next) => {
next(error) next(error)
} }
} }
export const getContactsByCompany = async (req, res, next) => {
try {
const { companyId } = req.params
const contacts = await personalContactService.getContactsByCompanyId(companyId)
res.status(200).json({ success: true, data: contacts })
} catch (error) {
next(error)
}
}

View File

@@ -79,10 +79,11 @@ export const contacts = pgTable('contacts', {
accountEmailUnique: unique('account_email_unique').on(table.emailAccountId, table.email), accountEmailUnique: unique('account_email_unique').on(table.emailAccountId, table.email),
})); }));
// Personal contacts - osobné kontakty používateľa (bez väzby na firmu alebo email account) // Personal contacts - osobné kontakty používateľa (s voliteľnou väzbou na firmu)
export const personalContacts = pgTable('personal_contacts', { export const personalContacts = pgTable('personal_contacts', {
id: uuid('id').primaryKey().defaultRandom(), id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
companyId: uuid('company_id').references(() => companies.id, { onDelete: 'set null' }), // voliteľná väzba na firmu
firstName: text('first_name').notNull(), firstName: text('first_name').notNull(),
lastName: text('last_name'), lastName: text('last_name'),
phone: text('phone').notNull(), phone: text('phone').notNull(),

View File

@@ -1,5 +1,6 @@
import express from 'express'; import express from 'express';
import * as companyController from '../controllers/company.controller.js'; import * as companyController from '../controllers/company.controller.js';
import * as personalContactController from '../controllers/personal-contact.controller.js';
import { authenticate } from '../middlewares/auth/authMiddleware.js'; import { authenticate } from '../middlewares/auth/authMiddleware.js';
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js'; import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
import { checkCompanyAccess } from '../middlewares/auth/resourceAccessMiddleware.js'; import { checkCompanyAccess } from '../middlewares/auth/resourceAccessMiddleware.js';
@@ -188,4 +189,12 @@ router.delete(
companyController.removeUserFromCompany companyController.removeUserFromCompany
); );
// Company Contacts (Personal contacts linked to company)
router.get(
'/:companyId/contacts',
validateParams(z.object({ companyId: z.string().uuid() })),
checkCompanyAccess,
personalContactController.getContactsByCompany
);
export default router; export default router;

View File

@@ -12,6 +12,7 @@ const createContactSchema = z.object({
phone: z.string().min(3, 'Telefón je povinný'), phone: z.string().min(3, 'Telefón je povinný'),
email: z.string().email('Neplatný email'), email: z.string().email('Neplatný email'),
secondaryEmail: z.union([z.string().email('Neplatný email'), z.literal('')]).optional(), secondaryEmail: z.union([z.string().email('Neplatný email'), z.literal('')]).optional(),
companyId: z.union([z.string().uuid(), z.literal(''), z.null()]).optional(),
}) })
const updateContactSchema = createContactSchema.partial() const updateContactSchema = createContactSchema.partial()

View File

@@ -1,13 +1,36 @@
import { db } from '../config/database.js' import { db } from '../config/database.js'
import { personalContacts } from '../db/schema.js' import { personalContacts, companies } from '../db/schema.js'
import { eq, and, desc, ne } from 'drizzle-orm' import { eq, and, desc, ne } from 'drizzle-orm'
import { ConflictError, NotFoundError } from '../utils/errors.js' import { ConflictError, NotFoundError } from '../utils/errors.js'
export const listPersonalContacts = async (userId) => { export const listPersonalContacts = async (userId) => {
const contacts = await db
.select({
id: personalContacts.id,
userId: personalContacts.userId,
companyId: personalContacts.companyId,
firstName: personalContacts.firstName,
lastName: personalContacts.lastName,
phone: personalContacts.phone,
email: personalContacts.email,
secondaryEmail: personalContacts.secondaryEmail,
createdAt: personalContacts.createdAt,
updatedAt: personalContacts.updatedAt,
companyName: companies.name,
})
.from(personalContacts)
.leftJoin(companies, eq(personalContacts.companyId, companies.id))
.where(eq(personalContacts.userId, userId))
.orderBy(desc(personalContacts.updatedAt))
return contacts
}
export const getContactsByCompanyId = async (companyId) => {
return db return db
.select() .select()
.from(personalContacts) .from(personalContacts)
.where(eq(personalContacts.userId, userId)) .where(eq(personalContacts.companyId, companyId))
.orderBy(desc(personalContacts.updatedAt)) .orderBy(desc(personalContacts.updatedAt))
} }
@@ -40,6 +63,7 @@ export const createPersonalContact = async (userId, contact) => {
.insert(personalContacts) .insert(personalContacts)
.values({ .values({
userId, userId,
companyId: contact.companyId || null,
firstName: contact.firstName, firstName: contact.firstName,
lastName: contact.lastName || null, lastName: contact.lastName || null,
phone: contact.phone, phone: contact.phone,
@@ -79,6 +103,7 @@ export const updatePersonalContact = async (contactId, userId, updates) => {
if (updates.phone !== undefined) updateData.phone = updates.phone if (updates.phone !== undefined) updateData.phone = updates.phone
if (updates.email !== undefined) updateData.email = updates.email if (updates.email !== undefined) updateData.email = updates.email
if (updates.secondaryEmail !== undefined) updateData.secondaryEmail = updates.secondaryEmail if (updates.secondaryEmail !== undefined) updateData.secondaryEmail = updates.secondaryEmail
if (updates.companyId !== undefined) updateData.companyId = updates.companyId || null
const [updated] = await db const [updated] = await db
.update(personalContacts) .update(personalContacts)