Merge pull request #218 from the-luap/fix/optional-email-event-creation
fix: respect optional email settings in event creation
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
exports.up = async function(knex) {
|
||||||
|
await knex.schema.alterTable('events', (table) => {
|
||||||
|
table.string('host_email', 255).nullable().alter();
|
||||||
|
table.string('admin_email', 255).nullable().alter();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = async function(knex) {
|
||||||
|
await knex.schema.alterTable('events', (table) => {
|
||||||
|
table.string('host_email', 255).notNullable().defaultTo('').alter();
|
||||||
|
table.string('admin_email', 255).notNullable().defaultTo('').alter();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -162,10 +162,10 @@ router.post('/', adminAuth, requirePermission('events.create'), [
|
|||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
body('event_name').notEmpty().trim(),
|
body('event_name').notEmpty().trim(),
|
||||||
body('event_date').optional().isDate(),
|
body('event_date').optional({ values: 'falsy' }).isDate(),
|
||||||
body('customer_name').optional().trim(),
|
body('customer_name').optional().trim(),
|
||||||
body('customer_email').optional().isEmail().normalizeEmail(),
|
body('customer_email').optional({ values: 'falsy' }).isEmail().normalizeEmail(),
|
||||||
body('admin_email').optional().isEmail().normalizeEmail(),
|
body('admin_email').optional({ values: 'falsy' }).isEmail().normalizeEmail(),
|
||||||
body('require_password').optional().isBoolean(),
|
body('require_password').optional().isBoolean(),
|
||||||
body('password').optional().isString().custom((value, { req }) => {
|
body('password').optional().isString().custom((value, { req }) => {
|
||||||
const input = req.body.require_password;
|
const input = req.body.require_password;
|
||||||
@@ -392,9 +392,9 @@ router.post('/', adminAuth, requirePermission('events.create'), [
|
|||||||
event_name,
|
event_name,
|
||||||
event_date: event_date || null,
|
event_date: event_date || null,
|
||||||
...(customerColumnsAvailable ? { customer_name: customerName, customer_email: customerEmail } : {}),
|
...(customerColumnsAvailable ? { customer_name: customerName, customer_email: customerEmail } : {}),
|
||||||
host_name: customerName,
|
host_name: customerName || null,
|
||||||
host_email: customerEmail,
|
host_email: customerEmail || null,
|
||||||
admin_email,
|
admin_email: admin_email || null,
|
||||||
password_hash,
|
password_hash,
|
||||||
welcome_message,
|
welcome_message,
|
||||||
color_theme,
|
color_theme,
|
||||||
@@ -446,28 +446,30 @@ router.post('/', adminAuth, requirePermission('events.create'), [
|
|||||||
{ type: 'admin', id: req.admin.id, name: req.admin.username }
|
{ type: 'admin', id: req.admin.id, name: req.admin.username }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Queue creation email
|
// Queue creation email (only if there is a recipient)
|
||||||
// Language detection is handled by email processor
|
// Language detection is handled by email processor
|
||||||
|
|
||||||
await db('email_queue').insert({
|
if (customerEmail) {
|
||||||
event_id: eventId,
|
await db('email_queue').insert({
|
||||||
recipient_email: customerEmail,
|
event_id: eventId,
|
||||||
email_type: 'gallery_created',
|
recipient_email: customerEmail,
|
||||||
email_data: JSON.stringify({
|
email_type: 'gallery_created',
|
||||||
customer_name: customerName,
|
email_data: JSON.stringify({
|
||||||
customer_email: customerEmail,
|
customer_name: customerName,
|
||||||
host_name: customerName || (customerEmail ? customerEmail.split('@')[0] : null),
|
customer_email: customerEmail,
|
||||||
event_name,
|
host_name: customerName || (customerEmail ? customerEmail.split('@')[0] : null),
|
||||||
event_date: event_date, // Pass raw date - will be formatted by email processor
|
event_name,
|
||||||
gallery_link: shareUrl,
|
event_date: event_date, // Pass raw date - will be formatted by email processor
|
||||||
gallery_password: requirePassword ? password : 'No password required',
|
gallery_link: shareUrl,
|
||||||
expiry_date: expires_at ? expires_at.toISOString() : null, // Pass ISO string - will be formatted by email processor
|
gallery_password: requirePassword ? password : 'No password required',
|
||||||
welcome_message: welcome_message || ''
|
expiry_date: expires_at ? expires_at.toISOString() : null, // Pass ISO string - will be formatted by email processor
|
||||||
}),
|
welcome_message: welcome_message || ''
|
||||||
status: 'pending',
|
}),
|
||||||
created_at: new Date()
|
status: 'pending',
|
||||||
// scheduled_at will use default value
|
created_at: new Date()
|
||||||
});
|
// scheduled_at will use default value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
id: eventId,
|
id: eventId,
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { test, expect, Page } from '@playwright/test';
|
||||||
|
|
||||||
|
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || '[email protected]';
|
||||||
|
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'Admin!234';
|
||||||
|
|
||||||
|
async function getAdminToken(page: Page): Promise<string> {
|
||||||
|
const res = await page.request.post('/api/auth/admin/login', {
|
||||||
|
data: { username: ADMIN_EMAIL, password: ADMIN_PASSWORD },
|
||||||
|
});
|
||||||
|
expect(res.ok()).toBeTruthy();
|
||||||
|
const body = await res.json();
|
||||||
|
expect(body.token).toBeTruthy();
|
||||||
|
return body.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateEventSettings(
|
||||||
|
page: Page,
|
||||||
|
token: string,
|
||||||
|
settings: Record<string, boolean>
|
||||||
|
) {
|
||||||
|
const res = await page.request.put('/api/admin/settings/general', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: settings,
|
||||||
|
});
|
||||||
|
expect(res.ok()).toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe('Optional email fields in event creation (#217)', () => {
|
||||||
|
test('event creation succeeds with empty emails when set to optional', async ({ page }) => {
|
||||||
|
const token = await getAdminToken(page);
|
||||||
|
|
||||||
|
// Disable email requirements
|
||||||
|
await updateEventSettings(page, token, {
|
||||||
|
event_require_customer_email: false,
|
||||||
|
event_require_admin_email: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create event with empty email fields
|
||||||
|
const eventRes = await page.request.post('/api/admin/events', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
event_type: 'wedding',
|
||||||
|
event_name: `E2E Optional Emails ${Date.now()}`,
|
||||||
|
event_date: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 10),
|
||||||
|
customer_name: 'Test Host',
|
||||||
|
customer_email: '',
|
||||||
|
admin_email: '',
|
||||||
|
password: 'TestPass123!',
|
||||||
|
expiration_days: 30,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = await eventRes.json();
|
||||||
|
expect(eventRes.ok(), `Expected 200 but got ${eventRes.status()}: ${JSON.stringify(body)}`).toBeTruthy();
|
||||||
|
expect(body.id).toBeTruthy();
|
||||||
|
|
||||||
|
// Cleanup: delete the created event
|
||||||
|
await page.request.delete(`/api/admin/events/${body.id}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
// Revert settings to required
|
||||||
|
await updateEventSettings(page, token, {
|
||||||
|
event_require_customer_email: true,
|
||||||
|
event_require_admin_email: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('event creation still fails with empty emails when set to required', async ({ page }) => {
|
||||||
|
const token = await getAdminToken(page);
|
||||||
|
|
||||||
|
// Ensure email requirements are enabled
|
||||||
|
await updateEventSettings(page, token, {
|
||||||
|
event_require_customer_email: true,
|
||||||
|
event_require_admin_email: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eventRes = await page.request.post('/api/admin/events', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
event_type: 'wedding',
|
||||||
|
event_name: `E2E Required Emails ${Date.now()}`,
|
||||||
|
event_date: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 10),
|
||||||
|
customer_name: 'Test Host',
|
||||||
|
customer_email: '',
|
||||||
|
admin_email: '',
|
||||||
|
password: 'TestPass123!',
|
||||||
|
expiration_days: 30,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(eventRes.status()).toBe(400);
|
||||||
|
const body = await eventRes.json();
|
||||||
|
const paths = body.errors.map((e: { path: string }) => e.path);
|
||||||
|
expect(paths).toContain('customer_email');
|
||||||
|
expect(paths).toContain('admin_email');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('event creation succeeds with missing email fields when optional', async ({ page }) => {
|
||||||
|
const token = await getAdminToken(page);
|
||||||
|
|
||||||
|
// Disable email requirements
|
||||||
|
await updateEventSettings(page, token, {
|
||||||
|
event_require_customer_email: false,
|
||||||
|
event_require_admin_email: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create event without email fields at all (undefined, not empty string)
|
||||||
|
const eventRes = await page.request.post('/api/admin/events', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
event_type: 'wedding',
|
||||||
|
event_name: `E2E Missing Emails ${Date.now()}`,
|
||||||
|
event_date: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 10),
|
||||||
|
customer_name: 'Test Host',
|
||||||
|
password: 'TestPass123!',
|
||||||
|
expiration_days: 30,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = await eventRes.json();
|
||||||
|
expect(eventRes.ok(), `Expected 200 but got ${eventRes.status()}: ${JSON.stringify(body)}`).toBeTruthy();
|
||||||
|
expect(body.id).toBeTruthy();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await page.request.delete(`/api/admin/events/${body.id}`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await updateEventSettings(page, token, {
|
||||||
|
event_require_customer_email: true,
|
||||||
|
event_require_admin_email: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user