feat: Mail template management UI and API CRUD

- Added full CRUD endpoints for mail templates (create, update, delete, preview)
- Introduced Joi validators for template create/update/preview
- Updated routes/controller to support ID and type lookups
- Built React Templates page with HTML editor, preview, and clipboard helpers
- Added navigation entry and route for /templates
- Enhanced documentation (README, QUICKSTART, KULLANIM, frontend/backend README)
This commit is contained in:
salvacybersec
2025-11-10 17:27:19 +03:00
parent dc16d0c549
commit 20191eb35d
11 changed files with 661 additions and 13 deletions

View File

@@ -1,6 +1,11 @@
const { MailTemplate } = require('../models');
const mailService = require('../services/mail.service');
const buildTemplateResponse = (template) => ({
success: true,
data: template,
});
// Get all templates
exports.getAllTemplates = async (req, res, next) => {
try {
@@ -17,6 +22,25 @@ exports.getAllTemplates = async (req, res, next) => {
}
};
// Get template by ID
exports.getTemplateById = async (req, res, next) => {
try {
const { id } = req.params;
const template = await MailTemplate.findByPk(id);
if (!template) {
return res.status(404).json({
success: false,
error: 'Template not found',
});
}
res.json(buildTemplateResponse(template));
} catch (error) {
next(error);
}
};
// Get template by type
exports.getTemplateByType = async (req, res, next) => {
try {
@@ -33,9 +57,71 @@ exports.getTemplateByType = async (req, res, next) => {
});
}
res.json(buildTemplateResponse(template));
} catch (error) {
next(error);
}
};
// Create template
exports.createTemplate = async (req, res, next) => {
try {
const template = await MailTemplate.create(req.body);
res.status(201).json(buildTemplateResponse(template));
} catch (error) {
if (error.name === 'SequelizeUniqueConstraintError') {
return res.status(400).json({
success: false,
error: 'Bu template_type değeri zaten kullanılıyor.',
});
}
next(error);
}
};
// Update template
exports.updateTemplate = async (req, res, next) => {
try {
const { id } = req.params;
const template = await MailTemplate.findByPk(id);
if (!template) {
return res.status(404).json({
success: false,
error: 'Template not found',
});
}
await template.update(req.body);
res.json(buildTemplateResponse(template));
} catch (error) {
if (error.name === 'SequelizeUniqueConstraintError') {
return res.status(400).json({
success: false,
error: 'Bu template_type değeri zaten kullanılıyor.',
});
}
next(error);
}
};
// Delete template
exports.deleteTemplate = async (req, res, next) => {
try {
const { id } = req.params;
const template = await MailTemplate.findByPk(id);
if (!template) {
return res.status(404).json({
success: false,
error: 'Template not found',
});
}
await template.destroy();
res.json({
success: true,
data: template,
message: 'Template deleted successfully',
});
} catch (error) {
next(error);
@@ -68,5 +154,4 @@ exports.previewTemplate = async (req, res, next) => {
}
};
module.exports = exports;
module.exports = exports;

View File

@@ -2,13 +2,22 @@ const express = require('express');
const router = express.Router();
const templateController = require('../controllers/template.controller');
const { requireAuth } = require('../middlewares/auth');
const {
validateCreateTemplate,
validateUpdateTemplate,
validatePreviewTemplate,
} = require('../validators/template.validator');
// All template routes require authentication
router.use(requireAuth);
router.get('/', templateController.getAllTemplates);
router.get('/:type', templateController.getTemplateByType);
router.post('/preview', templateController.previewTemplate);
router.post('/', validateCreateTemplate, templateController.createTemplate);
router.get('/:id(\\d+)', templateController.getTemplateById);
router.put('/:id(\\d+)', validateUpdateTemplate, templateController.updateTemplate);
router.delete('/:id(\\d+)', templateController.deleteTemplate);
router.get('/type/:type', templateController.getTemplateByType);
router.post('/preview', validatePreviewTemplate, templateController.previewTemplate);
module.exports = router;

View File

@@ -0,0 +1,63 @@
const Joi = require('joi');
const baseSchema = {
name: Joi.string().max(255).required(),
template_type: Joi.string().max(50).required(),
subject_template: Joi.string().max(500).allow('', null),
body_html: Joi.string().required(),
description: Joi.string().allow('', null),
active: Joi.boolean().default(true),
};
const createSchema = Joi.object(baseSchema);
const updateSchema = Joi.object({
...baseSchema,
});
const previewSchema = Joi.object({
template_html: Joi.string().required(),
company_name: Joi.string().allow('', null),
employee_name: Joi.string().allow('', null),
});
exports.validateCreateTemplate = (req, res, next) => {
const { error, value } = createSchema.validate(req.body, { abortEarly: false });
if (error) {
return res.status(400).json({
success: false,
error: 'Validation failed',
details: error.details.map((detail) => detail.message),
});
}
req.body = value;
next();
};
exports.validateUpdateTemplate = (req, res, next) => {
const { error, value } = updateSchema.validate(req.body, { abortEarly: false });
if (error) {
return res.status(400).json({
success: false,
error: 'Validation failed',
details: error.details.map((detail) => detail.message),
});
}
req.body = value;
next();
};
exports.validatePreviewTemplate = (req, res, next) => {
const { error, value } = previewSchema.validate(req.body, { abortEarly: false });
if (error) {
return res.status(400).json({
success: false,
error: 'Validation failed',
details: error.details.map((detail) => detail.message),
});
}
req.body = value;
next();
};