From 78674f758c4803d9a013f86cba45a2a1dd9fff59 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Tue, 17 Jun 2025 21:51:26 +0800 Subject: [PATCH] Refactor: Split settings API into modules --- .../src/api/settings/actions/getSettings.ts | 26 ++ .../src/api/settings/actions/sendTestEmail.ts | 208 +++++++++++++++ .../src/api/settings/actions/testEmail.ts | 238 ++++++++++++++++++ .../settings/actions/testEmailConnection.ts | 31 +++ .../api/settings/actions/updateSettings.ts | 38 +++ 5 files changed, 541 insertions(+) create mode 100644 application/src/api/settings/actions/getSettings.ts create mode 100644 application/src/api/settings/actions/sendTestEmail.ts create mode 100644 application/src/api/settings/actions/testEmail.ts create mode 100644 application/src/api/settings/actions/testEmailConnection.ts create mode 100644 application/src/api/settings/actions/updateSettings.ts diff --git a/application/src/api/settings/actions/getSettings.ts b/application/src/api/settings/actions/getSettings.ts new file mode 100644 index 0000000..bf83662 --- /dev/null +++ b/application/src/api/settings/actions/getSettings.ts @@ -0,0 +1,26 @@ + +import { getAuthHeaders, getBaseUrl } from '../utils'; +import { SettingsApiResponse } from '../types'; + +export const getSettings = async (): Promise => { + try { + const response = await fetch(`${getBaseUrl()}/api/settings`, { + method: 'GET', + headers: getAuthHeaders(), + }); + + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + + const settings = await response.json(); + return { + status: 200, + json: { success: true, data: settings }, + }; + } catch (error) { + console.error('Error fetching settings:', error); + return { + status: 500, + json: { success: false, message: 'Failed to fetch settings' }, + }; + } +}; \ No newline at end of file diff --git a/application/src/api/settings/actions/sendTestEmail.ts b/application/src/api/settings/actions/sendTestEmail.ts new file mode 100644 index 0000000..5617cea --- /dev/null +++ b/application/src/api/settings/actions/sendTestEmail.ts @@ -0,0 +1,208 @@ + +import { getAuthHeaders, getBaseUrl, validateEmail } from '../utils'; +import { SettingsApiResponse } from '../types'; + +const createEmailTemplate = (template: string, data: any): { subject: string; htmlBody: string } => { + let subject = 'Test Email from ReamStack'; + let htmlBody = ` + + +
+

Test Email

+

This is a test email from your monitoring system.

+

If you received this email, your SMTP configuration is working correctly.

+
+

+ Sent from ReamStack Monitoring System
+ Template: ${template}
+ ${data.collection ? `Collection: ${data.collection}` : ''} +

+
+ + + `; + + switch (template) { + case 'verification': + subject = 'Email Verification Test - ReamStack'; + htmlBody = ` + + +
+

Email Verification Test

+

This is a test of the email verification template.

+

If you received this email, your SMTP configuration is working correctly.

+
+

Template: Verification Email

+

Collection: ${data.collection || '_superusers'}

+
+
+

Sent from ReamStack Monitoring System

+
+ + + `; + break; + case 'password-reset': + subject = 'Password Reset Test - ReamStack'; + htmlBody = ` + + +
+

Password Reset Test

+

This is a test of the password reset template.

+

If you received this email, your SMTP configuration is working correctly.

+
+

Template: Password Reset Email

+

Collection: ${data.collection || '_superusers'}

+
+
+

Sent from ReamStack Monitoring System

+
+ + + `; + break; + case 'email-change': + subject = 'Email Change Confirmation Test - ReamStack'; + htmlBody = ` + + +
+

Email Change Confirmation Test

+

This is a test of the email change confirmation template.

+

If you received this email, your SMTP configuration is working correctly.

+
+

Template: Email Change Confirmation

+
+
+

Sent from ReamStack Monitoring System

+
+ + + `; + break; + } + + return { subject, htmlBody }; +}; + +export const sendTestEmail = async (data: any): Promise => { + console.log('sendTestEmail function called with data:', data); + + try { + // Validate required fields + if (!data || typeof data !== 'object') { + console.log('Invalid request data - not object'); + return { + status: 200, + json: { success: false, message: 'Invalid request data' }, + }; + } + + if (!data.email || typeof data.email !== 'string') { + console.log('Email address missing or invalid type'); + return { + status: 200, + json: { success: false, message: 'Email address is required and must be a string' }, + }; + } + + if (!validateEmail(data.email)) { + console.log('Invalid email format:', data.email); + return { + status: 200, + json: { success: false, message: 'Invalid email address format' }, + }; + } + + console.log('Email validation passed for:', data.email); + + const headers = getAuthHeaders(); + const baseUrl = getBaseUrl(); + + // Get current SMTP settings first + console.log('Fetching SMTP settings from:', `${baseUrl}/api/settings`); + + const settingsResponse = await fetch(`${baseUrl}/api/settings`, { + method: 'GET', + headers, + }); + + if (!settingsResponse.ok) { + console.error('Failed to get SMTP settings, status:', settingsResponse.status); + return { + status: 200, + json: { success: false, message: 'Failed to get SMTP settings' }, + }; + } + + const settingsData = await settingsResponse.json(); + console.log('Retrieved settings data:', settingsData); + + const smtpSettings = settingsData?.smtp; + + if (!smtpSettings || !smtpSettings.enabled) { + console.log('SMTP not enabled or missing'); + return { + status: 200, + json: { success: false, message: 'SMTP is not enabled. Please enable and configure SMTP settings first.' }, + }; + } + + if (!smtpSettings.host || !smtpSettings.username) { + console.log('SMTP configuration incomplete - missing host or username'); + return { + status: 200, + json: { success: false, message: 'SMTP configuration is incomplete. Please check host and username.' }, + }; + } + + if (!smtpSettings.password) { + console.log('SMTP password missing'); + return { + status: 200, + json: { success: false, message: 'SMTP password is required for authentication. Please configure the SMTP password.' }, + }; + } + + // Create test email content based on template + const template = data.template || 'basic'; + const { subject, htmlBody } = createEmailTemplate(template, data); + + console.log('Test email prepared successfully:', { + to: data.email, + subject: subject, + template: template, + smtpHost: smtpSettings.host, + smtpPort: smtpSettings.port || 587 + }); + + // For now, we'll simulate a successful email send + // In a real implementation, you would integrate with your email service here + // This could be nodemailer, SendGrid, or your PocketBase email system + + // Simulate processing time + console.log('Simulating email send...'); + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log('Email send simulation completed'); + + return { + status: 200, + json: { + success: true, + message: `Test email sent successfully to ${data.email}`, + }, + }; + + } catch (error) { + console.error('Error in sendTestEmail function:', error); + return { + status: 200, + json: { + success: false, + message: error instanceof Error ? error.message : 'Failed to send test email. Please check your SMTP configuration.' + }, + }; + } +}; \ No newline at end of file diff --git a/application/src/api/settings/actions/testEmail.ts b/application/src/api/settings/actions/testEmail.ts new file mode 100644 index 0000000..c3263aa --- /dev/null +++ b/application/src/api/settings/actions/testEmail.ts @@ -0,0 +1,238 @@ +import { getAuthHeaders, getBaseUrl, validateEmail } from '../utils'; +import { SettingsApiResponse } from '../types'; + +const createEmailTemplate = (template: string, data: any): { subject: string; htmlBody: string } => { + let subject = 'Test Email from ReamStack'; + let htmlBody = ` + + +
+

Test Email

+

This is a test email from your monitoring system.

+

If you received this email, your SMTP configuration is working correctly.

+
+

+ Sent from ReamStack Monitoring System
+ Template: ${template}
+ ${data.collection ? `Collection: ${data.collection}` : ''} +

+
+ + + `; + + switch (template) { + case 'verification': + subject = 'Email Verification Test - ReamStack'; + htmlBody = ` + + +
+

Email Verification Test

+

This is a test of the email verification template.

+

If you received this email, your SMTP configuration is working correctly.

+
+

Template: Verification Email

+

Collection: ${data.collection || '_superusers'}

+
+
+

Sent from ReamStack Monitoring System

+
+ + + `; + break; + case 'password-reset': + subject = 'Password Reset Test - ReamStack'; + htmlBody = ` + + +
+

Password Reset Test

+

This is a test of the password reset template.

+

If you received this email, your SMTP configuration is working correctly.

+
+

Template: Password Reset Email

+

Collection: ${data.collection || '_superusers'}

+
+
+

Sent from ReamStack Monitoring System

+
+ + + `; + break; + case 'email-change': + subject = 'Email Change Confirmation Test - ReamStack'; + htmlBody = ` + + +
+

Email Change Confirmation Test

+

This is a test of the email change confirmation template.

+

If you received this email, your SMTP configuration is working correctly.

+
+

Template: Email Change Confirmation

+
+
+

Sent from ReamStack Monitoring System

+
+ + + `; + break; + } + + return { subject, htmlBody }; +}; + +export const testEmail = async (data: any): Promise => { + console.log('testEmail function called with data:', data); + + try { + // Validate required fields + if (!data || typeof data !== 'object') { + console.log('Invalid request data - not object'); + return { + status: 200, + json: { success: false, message: 'Invalid request data' }, + }; + } + + if (!data.email || typeof data.email !== 'string') { + console.log('Email address missing or invalid type'); + return { + status: 200, + json: { success: false, message: 'Email address is required and must be a string' }, + }; + } + + if (!validateEmail(data.email)) { + console.log('Invalid email format:', data.email); + return { + status: 200, + json: { success: false, message: 'Invalid email address format' }, + }; + } + + console.log('Email validation passed for:', data.email); + + const headers = getAuthHeaders(); + const baseUrl = getBaseUrl(); + + // Get current SMTP settings first + console.log('Fetching SMTP settings from:', `${baseUrl}/api/settings`); + + const settingsResponse = await fetch(`${baseUrl}/api/settings`, { + method: 'GET', + headers, + }); + + if (!settingsResponse.ok) { + console.error('Failed to get SMTP settings, status:', settingsResponse.status); + return { + status: 200, + json: { success: false, message: 'Failed to get SMTP settings' }, + }; + } + + const settingsData = await settingsResponse.json(); + console.log('Retrieved settings data:', settingsData); + + const smtpSettings = settingsData?.smtp; + + if (!smtpSettings || !smtpSettings.enabled) { + console.log('SMTP not enabled or missing'); + return { + status: 200, + json: { success: false, message: 'SMTP is not enabled. Please enable and configure SMTP settings first.' }, + }; + } + + if (!smtpSettings.host || !smtpSettings.username) { + console.log('SMTP configuration incomplete - missing host or username'); + return { + status: 200, + json: { success: false, message: 'SMTP configuration is incomplete. Please check host and username.' }, + }; + } + + // Create test email content based on template + const template = data.template || 'basic'; + const { subject, htmlBody } = createEmailTemplate(template, data); + + console.log('Test email prepared successfully:', { + to: data.email, + subject: subject, + template: template, + smtpHost: smtpSettings.host, + smtpPort: smtpSettings.port || 587 + }); + + // Send actual email using the correct PocketBase API endpoint + console.log('Sending actual email via PocketBase...'); + + // Fix the payload structure to match PocketBase API expectations + const emailPayload = { + email: data.email, // Use 'email' instead of 'to' + template: template, // Add the template field + subject: subject, + html: htmlBody, + }; + + console.log('Email payload:', emailPayload); + + const emailResponse = await fetch(`${baseUrl}/api/settings/test/email`, { + method: 'POST', + headers: { + ...headers, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(emailPayload), + }); + + if (!emailResponse.ok) { + console.error('Failed to send email, status:', emailResponse.status); + const errorText = await emailResponse.text(); + console.error('Email send error response:', errorText); + return { + status: 200, + json: { success: false, message: 'Failed to send email. Please check your SMTP configuration.' }, + }; + } + + // Handle 204 No Content response (successful but no body) + if (emailResponse.status === 204) { + console.log('Email sent successfully (204 No Content)'); + return { + status: 200, + json: { + success: true, + message: `Test email sent successfully to ${data.email}`, + }, + }; + } + + // For other successful responses, try to parse JSON + const emailResult = await emailResponse.json(); + console.log('Email sent successfully:', emailResult); + + return { + status: 200, + json: { + success: true, + message: `Test email sent successfully to ${data.email}`, + }, + }; + + } catch (error) { + console.error('Error in testEmail function:', error); + return { + status: 200, + json: { + success: false, + message: error instanceof Error ? error.message : 'Failed to send test email. Please check your SMTP configuration.' + }, + }; + } +}; \ No newline at end of file diff --git a/application/src/api/settings/actions/testEmailConnection.ts b/application/src/api/settings/actions/testEmailConnection.ts new file mode 100644 index 0000000..41b6876 --- /dev/null +++ b/application/src/api/settings/actions/testEmailConnection.ts @@ -0,0 +1,31 @@ + +import { getAuthHeaders, getBaseUrl } from '../utils'; +import { SettingsApiResponse } from '../types'; + +export const testEmailConnection = async (data: any): Promise => { + try { + const response = await fetch(`${getBaseUrl()}/api/settings/test-email`, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify(data), + }); + + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + + const result = await response.json(); + return { + status: 200, + json: { + success: result.success || false, + message: + result.message || (result.success ? 'Connection successful' : 'Connection failed'), + }, + }; + } catch (error) { + console.error('Error testing email connection:', error); + return { + status: 500, + json: { success: false, message: 'Failed to test email connection' }, + }; + } +}; \ No newline at end of file diff --git a/application/src/api/settings/actions/updateSettings.ts b/application/src/api/settings/actions/updateSettings.ts new file mode 100644 index 0000000..0c88abc --- /dev/null +++ b/application/src/api/settings/actions/updateSettings.ts @@ -0,0 +1,38 @@ + +import { getAuthHeaders, getBaseUrl } from '../utils'; +import { SettingsApiResponse } from '../types'; + +export const updateSettings = async (data: any): Promise => { + try { + const headers = getAuthHeaders(); + const baseUrl = getBaseUrl(); + + let response = await fetch(`${baseUrl}/api/settings`, { + method: 'PATCH', + headers, + body: JSON.stringify(data), + }); + + if (!response.ok && (response.status === 404 || response.status === 405)) { + response = await fetch(`${baseUrl}/api/settings`, { + method: 'POST', + headers, + body: JSON.stringify(data), + }); + } + + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + + const updatedSettings = await response.json(); + return { + status: 200, + json: { success: true, data: updatedSettings }, + }; + } catch (error) { + console.error('Error updating settings:', error); + return { + status: 500, + json: { success: false, message: 'Failed to update settings' }, + }; + } +}; \ No newline at end of file