@@ -3,6 +3,9 @@ const { sendEmail } = require('../utils/SendEmail');
33const logger = require ( '../utils/logger' ) ;
44const mongoose = require ( 'mongoose' ) ;
55const EmailEvent = require ( '../models/EmailEvent' ) ;
6+ const { sendBulkEmails, getCampaignStatus, getUserCampaigns } = require ( '../services/bulkEmailService' ) ;
7+ const csvParser = require ( 'csv-parser' ) ;
8+ const { Readable } = require ( 'stream' ) ;
69
710// POST /api/emails/send
811// Body: { to, subject, text, html, from }
@@ -162,3 +165,152 @@ exports.testEthereal = async (req, res) => {
162165 } ) ;
163166 }
164167} ;
168+
169+ // POST /api/emails/bulk-send
170+ exports . bulkSend = async ( req , res ) => {
171+ try {
172+ const { name, subject, html, text } = req . body ;
173+ let recipients = [ ] ;
174+
175+ if ( req . file && req . file . buffer ) {
176+ recipients = await parseCSVFile ( req . file . buffer ) ;
177+ } else if ( req . body . recipients ) {
178+ try {
179+ recipients = typeof req . body . recipients === 'string'
180+ ? JSON . parse ( req . body . recipients )
181+ : req . body . recipients ;
182+ } catch ( e ) {
183+ return res . status ( 400 ) . json ( {
184+ success : false ,
185+ error : 'Invalid recipients format. Expected JSON array or CSV file.'
186+ } ) ;
187+ }
188+ } else {
189+ return res . status ( 400 ) . json ( {
190+ success : false ,
191+ error : 'Either CSV file or recipients array is required'
192+ } ) ;
193+ }
194+
195+ if ( ! name || ! subject ) {
196+ return res . status ( 400 ) . json ( {
197+ success : false ,
198+ error : 'Campaign name and subject are required'
199+ } ) ;
200+ }
201+ if ( ! html && ! text ) {
202+ return res . status ( 400 ) . json ( {
203+ success : false ,
204+ error : 'Either HTML or text content is required'
205+ } ) ;
206+ }
207+ if ( ! recipients || recipients . length === 0 ) {
208+ return res . status ( 400 ) . json ( {
209+ success : false ,
210+ error : 'No valid recipients found in CSV or recipients array'
211+ } ) ;
212+ }
213+ const emailRegex = / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / ;
214+ const validRecipients = recipients . filter ( r => {
215+ if ( ! r . email || ! emailRegex . test ( r . email ) ) {
216+ logger . warn ( `Invalid email address: ${ r . email } ` ) ;
217+ return false ;
218+ }
219+ return true ;
220+ } ) ;
221+ if ( validRecipients . length === 0 ) {
222+ return res . status ( 400 ) . json ( {
223+ success : false ,
224+ error : 'No valid email addresses found'
225+ } ) ;
226+ }
227+
228+ const userId = req . user ?. id || null ;
229+
230+ // Send bulk emails
231+ const result = await sendBulkEmails (
232+ { name, subject, html, text, userId } ,
233+ validRecipients
234+ ) ;
235+ return res . status ( 200 ) . json ( {
236+ success : true ,
237+ ...result
238+ } ) ;
239+
240+ } catch ( error ) {
241+ logger . error ( 'Error in bulk send controller:' , error && ( error . stack || error ) ) ;
242+ return res . status ( 500 ) . json ( {
243+ success : false ,
244+ error : error && ( error . message || error )
245+ } ) ;
246+ }
247+ } ;
248+
249+ //function to parse CSV
250+ const parseCSVFile = ( buffer ) => {
251+ return new Promise ( ( resolve , reject ) => {
252+ const rows = [ ] ;
253+ const stream = Readable . from ( buffer . toString ( ) ) ;
254+
255+ stream
256+ . pipe ( csvParser ( {
257+ skipLines : 0 ,
258+ mapHeaders : ( { header } ) => header && header . trim ( ) . toLowerCase ( )
259+ } ) )
260+ . on ( 'data' , ( row ) => {
261+ const email = ( row . email || row [ 'e-mail' ] || row [ 'email address' ] || '' ) . toString ( ) . trim ( ) . toLowerCase ( ) ;
262+ const name = ( row . name || row [ 'full name' ] || row [ 'fullname' ] || row [ 'first name' ] || '' ) . toString ( ) . trim ( ) ;
263+
264+ if ( email ) {
265+ rows . push ( { email, name } ) ;
266+ }
267+ } )
268+ . on ( 'end' , ( ) => {
269+ resolve ( rows ) ;
270+ } )
271+ . on ( 'error' , ( err ) => {
272+ reject ( err ) ;
273+ } ) ;
274+ } ) ;
275+ } ;
276+
277+ // GET /api/emails/campaign/:id
278+ exports . getCampaign = async ( req , res ) => {
279+ try {
280+ const { id } = req . params ;
281+ const campaign = await getCampaignStatus ( id ) ;
282+
283+ return res . status ( 200 ) . json ( {
284+ success : true ,
285+ campaign
286+ } ) ;
287+ } catch ( error ) {
288+ logger . error ( 'Error getting campaign:' , error && ( error . stack || error ) ) ;
289+ return res . status ( 500 ) . json ( {
290+ success : false ,
291+ error : error && ( error . message || error )
292+ } ) ;
293+ }
294+ } ;
295+
296+ // GET /api/emails/campaigns
297+ exports . getCampaigns = async ( req , res ) => {
298+ try {
299+ const page = Math . max ( 1 , parseInt ( req . query . page , 10 ) || 1 ) ;
300+ const limit = Math . max ( 1 , parseInt ( req . query . limit , 10 ) || 20 ) ;
301+ const userId = req . user ?. id || null ;
302+
303+ const result = await getUserCampaigns ( userId , page , limit ) ;
304+
305+ return res . status ( 200 ) . json ( {
306+ success : true ,
307+ ...result
308+ } ) ;
309+ } catch ( error ) {
310+ logger . error ( 'Error getting campaigns:' , error && ( error . stack || error ) ) ;
311+ return res . status ( 500 ) . json ( {
312+ success : false ,
313+ error : error && ( error . message || error )
314+ } ) ;
315+ }
316+ } ;
0 commit comments