diff --git a/src/app.js b/src/app.js index ff893c0..e5e5781 100644 --- a/src/app.js +++ b/src/app.js @@ -30,6 +30,7 @@ import eventRoutes from './routes/event.routes.js'; import messageRoutes from './routes/message.routes.js'; import userRoutes from './routes/user.routes.js'; import serviceRoutes from './routes/service.routes.js'; +import emailSignatureRoutes from './routes/email-signature.routes.js'; const app = express(); @@ -126,6 +127,7 @@ app.use('/api/events', eventRoutes); app.use('/api/messages', messageRoutes); app.use('/api/users', userRoutes); app.use('/api/services', serviceRoutes); +app.use('/api/email-signature', emailSignatureRoutes); // Basic route app.get('/', (req, res) => { diff --git a/src/controllers/email-signature.controller.js b/src/controllers/email-signature.controller.js new file mode 100644 index 0000000..79d5669 --- /dev/null +++ b/src/controllers/email-signature.controller.js @@ -0,0 +1,93 @@ +import * as emailSignatureService from '../services/email-signature.service.js'; + +/** + * Get current user's email signature + * GET /api/email-signature + */ +export const getSignature = async (req, res, next) => { + try { + const signature = await emailSignatureService.getSignature(req.userId); + + res.status(200).json({ + success: true, + data: signature, + }); + } catch (error) { + next(error); + } +}; + +/** + * Create or update email signature + * POST /api/email-signature + */ +export const upsertSignature = async (req, res, next) => { + try { + const signature = await emailSignatureService.upsertSignature(req.userId, req.body); + + res.status(200).json({ + success: true, + data: signature, + message: 'Podpis bol uložený', + }); + } catch (error) { + next(error); + } +}; + +/** + * Toggle signature on/off + * PATCH /api/email-signature/toggle + */ +export const toggleSignature = async (req, res, next) => { + try { + const { isEnabled } = req.body; + const signature = await emailSignatureService.toggleSignature(req.userId, isEnabled); + + res.status(200).json({ + success: true, + data: signature, + message: isEnabled ? 'Podpis zapnutý' : 'Podpis vypnutý', + }); + } catch (error) { + next(error); + } +}; + +/** + * Get formatted signature text for email + * GET /api/email-signature/formatted + */ +export const getFormattedSignature = async (req, res, next) => { + try { + const signature = await emailSignatureService.getSignature(req.userId); + const formatted = emailSignatureService.formatSignatureText(signature); + + res.status(200).json({ + success: true, + data: { + text: formatted, + isEnabled: signature?.isEnabled || false, + }, + }); + } catch (error) { + next(error); + } +}; + +/** + * Delete email signature + * DELETE /api/email-signature + */ +export const deleteSignature = async (req, res, next) => { + try { + const result = await emailSignatureService.deleteSignature(req.userId); + + res.status(200).json({ + success: true, + message: result.message, + }); + } catch (error) { + next(error); + } +}; diff --git a/src/db/schema.js b/src/db/schema.js index 01273ff..880f1dd 100644 --- a/src/db/schema.js +++ b/src/db/schema.js @@ -310,6 +310,21 @@ export const services = pgTable('services', { updatedAt: timestamp('updated_at').defaultNow().notNull(), }); +// Email Signatures table - email podpisy používateľov +export const emailSignatures = pgTable('email_signatures', { + id: uuid('id').primaryKey().defaultRandom(), + userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull().unique(), + fullName: text('full_name'), + position: text('position'), + phone: text('phone'), + email: text('email'), + companyName: text('company_name'), + website: text('website'), + isEnabled: boolean('is_enabled').default(true).notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + // Messages table - interná komunikácia medzi používateľmi export const messages = pgTable('messages', { id: uuid('id').primaryKey().defaultRandom(), diff --git a/src/routes/email-signature.routes.js b/src/routes/email-signature.routes.js new file mode 100644 index 0000000..fe04519 --- /dev/null +++ b/src/routes/email-signature.routes.js @@ -0,0 +1,42 @@ +import express from 'express'; +import * as emailSignatureController from '../controllers/email-signature.controller.js'; +import { authenticate } from '../middlewares/auth/authMiddleware.js'; +import { validateBody } from '../middlewares/security/validateInput.js'; +import { z } from 'zod'; + +const router = express.Router(); + +// All routes require authentication +router.use(authenticate); + +// Validation schemas +const signatureSchema = z.object({ + fullName: z.string().max(255).optional().or(z.literal('')).or(z.null()), + position: z.string().max(255).optional().or(z.literal('')).or(z.null()), + phone: z.string().max(50).optional().or(z.literal('')).or(z.null()), + email: z.string().email('Neplatný formát emailu').max(255).optional().or(z.literal('')).or(z.null()), + companyName: z.string().max(255).optional().or(z.literal('')).or(z.null()), + website: z.string().max(255).optional().or(z.literal('')).or(z.null()), + isEnabled: z.boolean().optional(), +}); + +const toggleSchema = z.object({ + isEnabled: z.boolean(), +}); + +// Get user's signature +router.get('/', emailSignatureController.getSignature); + +// Get formatted signature text +router.get('/formatted', emailSignatureController.getFormattedSignature); + +// Create or update signature +router.post('/', validateBody(signatureSchema), emailSignatureController.upsertSignature); + +// Toggle signature on/off +router.patch('/toggle', validateBody(toggleSchema), emailSignatureController.toggleSignature); + +// Delete signature +router.delete('/', emailSignatureController.deleteSignature); + +export default router; diff --git a/src/services/email-signature.service.js b/src/services/email-signature.service.js new file mode 100644 index 0000000..896f3ea --- /dev/null +++ b/src/services/email-signature.service.js @@ -0,0 +1,140 @@ +import { db } from '../config/database.js'; +import { emailSignatures } from '../db/schema.js'; +import { eq } from 'drizzle-orm'; +import { NotFoundError } from '../utils/errors.js'; + +/** + * Get signature for a user + */ +export const getSignature = async (userId) => { + const [signature] = await db + .select() + .from(emailSignatures) + .where(eq(emailSignatures.userId, userId)) + .limit(1); + + return signature || null; +}; + +/** + * Create or update signature for a user + */ +export const upsertSignature = async (userId, data) => { + const existing = await getSignature(userId); + + if (existing) { + // Update + const [updated] = await db + .update(emailSignatures) + .set({ + fullName: data.fullName || null, + position: data.position || null, + phone: data.phone || null, + email: data.email || null, + companyName: data.companyName || null, + website: data.website || null, + isEnabled: data.isEnabled !== undefined ? data.isEnabled : existing.isEnabled, + updatedAt: new Date(), + }) + .where(eq(emailSignatures.userId, userId)) + .returning(); + + return updated; + } else { + // Create + const [created] = await db + .insert(emailSignatures) + .values({ + userId, + fullName: data.fullName || null, + position: data.position || null, + phone: data.phone || null, + email: data.email || null, + companyName: data.companyName || null, + website: data.website || null, + isEnabled: data.isEnabled !== undefined ? data.isEnabled : true, + }) + .returning(); + + return created; + } +}; + +/** + * Toggle signature enabled/disabled + */ +export const toggleSignature = async (userId, isEnabled) => { + const existing = await getSignature(userId); + + if (!existing) { + throw new NotFoundError('Signature not found'); + } + + const [updated] = await db + .update(emailSignatures) + .set({ + isEnabled, + updatedAt: new Date(), + }) + .where(eq(emailSignatures.userId, userId)) + .returning(); + + return updated; +}; + +/** + * Format signature as plain text for email + */ +export const formatSignatureText = (signature) => { + if (!signature || !signature.isEnabled) { + return ''; + } + + const lines = []; + + if (signature.fullName) { + lines.push(signature.fullName); + } + + const positionLine = [signature.position, signature.companyName] + .filter(Boolean) + .join(' | '); + if (positionLine) { + lines.push(positionLine); + } + + if (signature.phone) { + lines.push(signature.phone); + } + + if (signature.email) { + lines.push(signature.email); + } + + if (signature.website) { + lines.push(signature.website); + } + + if (lines.length === 0) { + return ''; + } + + return '\n\n———\n' + lines.join('\n'); +}; + +/** + * Delete signature for a user + */ +export const deleteSignature = async (userId) => { + const existing = await getSignature(userId); + + if (!existing) { + throw new NotFoundError('Signature not found'); + } + + await db + .delete(emailSignatures) + .where(eq(emailSignatures.userId, userId)); + + return { message: 'Signature deleted' }; +};