1
+ /* eslint-disable unicorn/no-nested-ternary */
1
2
import { NextFunction , Request , Response } from 'express' ;
2
3
import Joi from 'joi' ;
4
+ import { badRequest , created } from '../../../utils/responses' ;
3
5
import { hasPermission } from '../../../middlewares/authentication' ;
4
- import { Error as ApiError , MailGeneralQuery } from '../../../types' ;
6
+ import { Error as ApiError , MailQuery } from '../../../types' ;
5
7
import { validateBody } from '../../../middlewares/validation' ;
6
- import { sendGeneralMail } from '../../../services/email' ;
8
+ import { sendEmail , SerializedMail } from '../../../services/email' ;
9
+ import { serialize } from '../../../services/email/serializer' ;
10
+ import database from '../../../services/database' ;
7
11
import { getRequestInfo } from '../../../utils/users' ;
8
12
9
13
export default [
@@ -12,7 +16,23 @@ export default [
12
16
validateBody (
13
17
Joi . object ( {
14
18
preview : Joi . boolean ( ) . default ( false ) ,
15
- generalMail : Joi . string ( ) . required ( ) ,
19
+ locked : Joi . boolean ( ) . optional ( ) ,
20
+ tournamentId : Joi . string ( ) . optional ( ) ,
21
+ subject : Joi . string ( ) . required ( ) ,
22
+ highlight : Joi . object ( {
23
+ title : Joi . string ( ) . required ( ) ,
24
+ intro : Joi . string ( ) . required ( ) ,
25
+ } ) . required ( ) ,
26
+ reason : Joi . string ( ) . optional ( ) ,
27
+ content : Joi . array ( )
28
+ . items (
29
+ Joi . object ( {
30
+ title : Joi . string ( ) . required ( ) ,
31
+ components : Joi . array ( ) . required ( ) ,
32
+ } ) . required ( ) ,
33
+ )
34
+ . required ( )
35
+ . error ( new Error ( ApiError . MalformedMailBody ) ) ,
16
36
} ) . error (
17
37
( errors ) =>
18
38
errors . find ( ( error ) => error . message === ApiError . MalformedMailBody ) ?? new Error ( ApiError . InvalidMailOptions ) ,
@@ -22,13 +42,100 @@ export default [
22
42
// Controller
23
43
async ( request : Request , response : Response , next : NextFunction ) => {
24
44
try {
25
- const mail = request . body as MailGeneralQuery ;
45
+ const mail = request . body as MailQuery ;
26
46
const { user } = getRequestInfo ( response ) ;
27
47
28
- const nbMailSent = await sendGeneralMail ( mail . generalMail , mail . preview ? user : null ) ;
48
+ // Find mail adresses to send the mail to
49
+ const mails = await database . user
50
+ . findMany ( {
51
+ where : {
52
+ registerToken : null ,
53
+ email : {
54
+ not : null ,
55
+ } ,
56
+ ...( mail . preview
57
+ ? {
58
+ id : user . id ,
59
+ }
60
+ : {
61
+ team : {
62
+ ...( mail . locked
63
+ ? {
64
+ NOT : {
65
+ lockedAt : null ,
66
+ } ,
67
+ }
68
+ : mail . locked === false
69
+ ? { lockedAt : null }
70
+ : { } ) ,
71
+ tournamentId : mail . tournamentId ,
72
+ } ,
73
+ } ) ,
74
+ } ,
75
+ select : {
76
+ email : true ,
77
+ } ,
78
+ } )
79
+ . then ( ( mailWrappers ) => mailWrappers . map ( ( mailWrapper ) => mailWrapper . email ) ) ;
29
80
30
- // TODO: change return to a created response
31
- return response . json ( { message : `Sent ${ nbMailSent } emails` } ) ;
81
+ // Parallelize mails as it may take time
82
+ // As every mail is generated on a user basis, we cannot catch
83
+ // a mail format error as simply as usual. This is the reason
84
+ // why we track the status of all sent mails.
85
+ // If all mails are errored due to invalid syntax, it is most
86
+ // likely that the sender did a mistake.
87
+ const outgoingMails = await Promise . allSettled (
88
+ mails . map ( async ( adress ) => {
89
+ let mailContent : SerializedMail ;
90
+ try {
91
+ mailContent = await serialize ( {
92
+ sections : mail . content ,
93
+ reason : mail . reason ,
94
+ title : {
95
+ banner : mail . subject ,
96
+ highlight : mail . highlight . title ,
97
+ short : mail . highlight . intro ,
98
+ topic : mail . preview ? `[PREVIEW]: ${ mail . subject } ` : mail . subject ,
99
+ } ,
100
+ receiver : adress ,
101
+ } ) ;
102
+ } catch {
103
+ throw ApiError . MalformedMailBody ;
104
+ }
105
+ return sendEmail ( mailContent ) ;
106
+ } ) ,
107
+ ) ;
108
+
109
+ // Counts mail statuses
110
+ const results = outgoingMails . reduce (
111
+ ( result , state ) => {
112
+ if ( state . status === 'fulfilled' )
113
+ return {
114
+ ...result ,
115
+ delivered : result . delivered + 1 ,
116
+ } ;
117
+ if ( state . reason === ApiError . MalformedMailBody )
118
+ return {
119
+ ...result ,
120
+ malformed : result . malformed + 1 ,
121
+ } ;
122
+ return {
123
+ ...result ,
124
+ undelivered : result . undelivered + 1 ,
125
+ } ;
126
+ } ,
127
+ { malformed : 0 , delivered : 0 , undelivered : 0 } ,
128
+ ) ;
129
+
130
+ // Respond to the request with the appropriate response code
131
+ if ( results . malformed && ! results . delivered && ! results . undelivered )
132
+ return badRequest ( response , ApiError . MalformedMailBody ) ;
133
+
134
+ if ( results . delivered || ! results . undelivered ) return created ( response , results ) ;
135
+
136
+ throw ( < PromiseRejectedResult > (
137
+ outgoingMails . find ( ( result ) => result . status === 'rejected' && result . reason !== ApiError . MalformedMailBody )
138
+ ) ) . reason ;
32
139
} catch ( error ) {
33
140
return next ( error ) ;
34
141
}
0 commit comments