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:
@@ -65,9 +65,13 @@ GET /t/:token - Tracking endpoint (IP, GeoIP, Telegram)
|
||||
|
||||
### Templates
|
||||
```
|
||||
GET /api/templates - Tüm şablonlar
|
||||
GET /api/templates/:type - Şablon detay
|
||||
POST /api/templates/preview - Önizleme
|
||||
GET /api/templates - Tüm şablonlar
|
||||
POST /api/templates - Yeni şablon oluştur
|
||||
GET /api/templates/:id - Şablon detay (ID)
|
||||
PUT /api/templates/:id - Şablon güncelle
|
||||
DELETE /api/templates/:id - Şablon sil
|
||||
GET /api/templates/type/:type - Şablon (type ile)
|
||||
POST /api/templates/preview - Önizleme
|
||||
```
|
||||
|
||||
### Settings
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
63
backend/src/validators/template.validator.js
Normal file
63
backend/src/validators/template.validator.js
Normal 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();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user