add contacts to crm & display on dashboard
This commit is contained in:
@@ -15,6 +15,7 @@ import { apiRateLimiter } from './middlewares/security/rateLimiter.js';
|
||||
import authRoutes from './routes/auth.routes.js';
|
||||
import adminRoutes from './routes/admin.routes.js';
|
||||
import contactRoutes from './routes/contact.routes.js';
|
||||
import personalContactRoutes from './routes/personal-contact.routes.js';
|
||||
import crmEmailRoutes from './routes/crm-email.routes.js';
|
||||
import emailAccountRoutes from './routes/email-account.routes.js';
|
||||
import timesheetRoutes from './routes/timesheet.routes.js';
|
||||
@@ -78,6 +79,7 @@ app.get('/health', (req, res) => {
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/admin', adminRoutes);
|
||||
app.use('/api/contacts', contactRoutes);
|
||||
app.use('/api/personal-contacts', personalContactRoutes);
|
||||
app.use('/api/emails', crmEmailRoutes);
|
||||
app.use('/api/email-accounts', emailAccountRoutes);
|
||||
app.use('/api/timesheets', timesheetRoutes);
|
||||
|
||||
59
src/controllers/personal-contact.controller.js
Normal file
59
src/controllers/personal-contact.controller.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as personalContactService from '../services/personal-contact.service.js'
|
||||
import { BadRequestError } from '../utils/errors.js'
|
||||
|
||||
const normalizePayload = (body) => ({
|
||||
firstName: body.firstName?.trim(),
|
||||
lastName: body.lastName?.trim() || null,
|
||||
phone: body.phone?.trim(),
|
||||
email: body.email?.trim(),
|
||||
secondaryEmail: body.secondaryEmail?.trim() || null,
|
||||
})
|
||||
|
||||
export const listPersonalContacts = async (req, res, next) => {
|
||||
try {
|
||||
const contacts = await personalContactService.listPersonalContacts(req.userId)
|
||||
res.status(200).json({ success: true, data: contacts })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const createPersonalContact = async (req, res, next) => {
|
||||
try {
|
||||
const payload = normalizePayload(req.body)
|
||||
if (!payload.firstName || !payload.phone || !payload.email) {
|
||||
throw new BadRequestError('firstName, phone a email sú povinné')
|
||||
}
|
||||
|
||||
const created = await personalContactService.createPersonalContact(req.userId, payload)
|
||||
|
||||
res.status(201).json({ success: true, data: created })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const updatePersonalContact = async (req, res, next) => {
|
||||
try {
|
||||
const payload = normalizePayload(req.body)
|
||||
|
||||
const updated = await personalContactService.updatePersonalContact(
|
||||
req.params.contactId,
|
||||
req.userId,
|
||||
payload
|
||||
)
|
||||
|
||||
res.status(200).json({ success: true, data: updated })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const deletePersonalContact = async (req, res, next) => {
|
||||
try {
|
||||
const result = await personalContactService.deletePersonalContact(req.params.contactId, req.userId)
|
||||
res.status(200).json({ success: true, message: result.message })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,21 @@ export const contacts = pgTable('contacts', {
|
||||
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)
|
||||
export const personalContacts = pgTable('personal_contacts', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
firstName: text('first_name').notNull(),
|
||||
lastName: text('last_name'),
|
||||
phone: text('phone').notNull(),
|
||||
email: text('email').notNull(),
|
||||
secondaryEmail: text('secondary_email'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
}, (table) => ({
|
||||
personalContactUniqueEmail: unique('personal_contact_user_email').on(table.userId, table.email),
|
||||
}));
|
||||
|
||||
// Emails table - uložené emaily z JMAP
|
||||
// Emaily patria k email accountu a sú zdieľané medzi všetkými používateľmi s prístupom
|
||||
export const emails = pgTable('emails', {
|
||||
|
||||
40
src/routes/personal-contact.routes.js
Normal file
40
src/routes/personal-contact.routes.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import express from 'express'
|
||||
import { z } from 'zod'
|
||||
import { authenticate } from '../middlewares/auth/authMiddleware.js'
|
||||
import { validateBody, validateParams } from '../middlewares/security/validateInput.js'
|
||||
import * as personalContactController from '../controllers/personal-contact.controller.js'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
const createContactSchema = z.object({
|
||||
firstName: z.string().min(1, 'Meno je povinné'),
|
||||
lastName: z.string().optional(),
|
||||
phone: z.string().min(3, 'Telefón je povinný'),
|
||||
email: z.string().email('Neplatný email'),
|
||||
secondaryEmail: z.union([z.string().email('Neplatný email'), z.literal('')]).optional(),
|
||||
})
|
||||
|
||||
const updateContactSchema = createContactSchema.partial()
|
||||
|
||||
const contactIdSchema = z.object({ contactId: z.string().uuid() })
|
||||
|
||||
router.use(authenticate)
|
||||
|
||||
router.get('/', personalContactController.listPersonalContacts)
|
||||
|
||||
router.post('/', validateBody(createContactSchema), personalContactController.createPersonalContact)
|
||||
|
||||
router.put(
|
||||
'/:contactId',
|
||||
validateParams(contactIdSchema),
|
||||
validateBody(updateContactSchema),
|
||||
personalContactController.updatePersonalContact
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/:contactId',
|
||||
validateParams(contactIdSchema),
|
||||
personalContactController.deletePersonalContact
|
||||
)
|
||||
|
||||
export default router
|
||||
104
src/services/personal-contact.service.js
Normal file
104
src/services/personal-contact.service.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { db } from '../config/database.js'
|
||||
import { personalContacts } from '../db/schema.js'
|
||||
import { eq, and, desc, ne } from 'drizzle-orm'
|
||||
import { ConflictError, NotFoundError } from '../utils/errors.js'
|
||||
|
||||
export const listPersonalContacts = async (userId) => {
|
||||
return db
|
||||
.select()
|
||||
.from(personalContacts)
|
||||
.where(eq(personalContacts.userId, userId))
|
||||
.orderBy(desc(personalContacts.updatedAt))
|
||||
}
|
||||
|
||||
const getContactById = async (contactId, userId) => {
|
||||
const [contact] = await db
|
||||
.select()
|
||||
.from(personalContacts)
|
||||
.where(and(eq(personalContacts.id, contactId), eq(personalContacts.userId, userId)))
|
||||
.limit(1)
|
||||
|
||||
if (!contact) {
|
||||
throw new NotFoundError('Kontakt nenájdený')
|
||||
}
|
||||
|
||||
return contact
|
||||
}
|
||||
|
||||
export const createPersonalContact = async (userId, contact) => {
|
||||
const [existing] = await db
|
||||
.select({ id: personalContacts.id })
|
||||
.from(personalContacts)
|
||||
.where(and(eq(personalContacts.userId, userId), eq(personalContacts.email, contact.email)))
|
||||
.limit(1)
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictError('Kontakt s týmto emailom už existuje')
|
||||
}
|
||||
|
||||
const [created] = await db
|
||||
.insert(personalContacts)
|
||||
.values({
|
||||
userId,
|
||||
firstName: contact.firstName,
|
||||
lastName: contact.lastName || null,
|
||||
phone: contact.phone,
|
||||
email: contact.email,
|
||||
secondaryEmail: contact.secondaryEmail || null,
|
||||
})
|
||||
.returning()
|
||||
|
||||
return created
|
||||
}
|
||||
|
||||
export const updatePersonalContact = async (contactId, userId, updates) => {
|
||||
await getContactById(contactId, userId)
|
||||
|
||||
if (updates.email) {
|
||||
const [existing] = await db
|
||||
.select({ id: personalContacts.id })
|
||||
.from(personalContacts)
|
||||
.where(
|
||||
and(
|
||||
eq(personalContacts.userId, userId),
|
||||
eq(personalContacts.email, updates.email),
|
||||
ne(personalContacts.id, contactId)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictError('Kontakt s týmto emailom už existuje')
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = { updatedAt: new Date() }
|
||||
|
||||
if (updates.firstName !== undefined) updateData.firstName = updates.firstName
|
||||
if (updates.lastName !== undefined) updateData.lastName = updates.lastName
|
||||
if (updates.phone !== undefined) updateData.phone = updates.phone
|
||||
if (updates.email !== undefined) updateData.email = updates.email
|
||||
if (updates.secondaryEmail !== undefined) updateData.secondaryEmail = updates.secondaryEmail
|
||||
|
||||
const [updated] = await db
|
||||
.update(personalContacts)
|
||||
.set(updateData)
|
||||
.where(and(eq(personalContacts.id, contactId), eq(personalContacts.userId, userId)))
|
||||
.returning()
|
||||
|
||||
if (!updated) {
|
||||
throw new NotFoundError('Kontakt nenájdený')
|
||||
}
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
export const deletePersonalContact = async (contactId, userId) => {
|
||||
await getContactById(contactId, userId)
|
||||
|
||||
await db
|
||||
.delete(personalContacts)
|
||||
.where(and(eq(personalContacts.id, contactId), eq(personalContacts.userId, userId)))
|
||||
|
||||
return { success: true, message: 'Kontakt bol odstránený' }
|
||||
}
|
||||
Reference in New Issue
Block a user