@@ -43,61 +43,80 @@ router.post(
4343 catchMiddlewareError ( async function postEvents ( req : ExtendedRequest , res : Response ) {
4444 noCacheControl ( res )
4545
46- // Make sure the type is supported before continuing
47- if ( ! req . body . type || ! allowedTypes . has ( req . body . type ) ) {
48- return res . status ( 400 ) . json ( { message : 'Invalid type' } )
49- }
50- const type : EventType = req . body . type
51- const body : EventProps & EventPropsByType [ EventType ] = req . body
46+ const eventsToProcess = Array . isArray ( req . body ) ? req . body : [ req . body ]
47+ const validEvents : any [ ] = [ ]
48+ const validationErrors : any [ ] = [ ]
49+
50+ for ( const eventBody of eventsToProcess ) {
51+ try {
52+ if ( ! eventBody . type || ! allowedTypes . has ( eventBody . type ) ) {
53+ validationErrors . push ( { event : eventBody , error : 'Invalid type' } )
54+ continue
55+ }
56+ const type : EventType = eventBody . type
57+ const body : EventProps & EventPropsByType [ EventType ] = eventBody
58+ if ( isSurvey ( body ) && body . survey_comment ) {
59+ body . survey_rating = await getSurveyCommentRating ( {
60+ comment : body . survey_comment ,
61+ language : body . context . path_language || 'en' ,
62+ } )
63+ body . survey_comment_language = await getGuessedLanguage ( body . survey_comment )
64+ }
5265
53- // Validate the data matches the corresponding data schema
54- const validate = validators [ type ]
55- if ( ! validate ( body ) ) {
56- // This protects so we don't bother sending the same validation
57- // error, per user, more than once (per time interval).
58- // This helps if we're bombarded with junk bot traffic. So it
59- // protects our Hydro instance from being overloaded with things
60- // that aren't helping anybody.
61- const hash = `${ req . ip } :${ ( validate . errors || [ ] )
62- . map ( ( error : ErrorObject ) => error . message + error . instancePath )
63- . join ( ':' ) } `
64- if ( ! sentValidationErrors . has ( hash ) ) {
65- sentValidationErrors . set ( hash , true )
66- // Track validation errors in Hydro so that we can know if
67- // there's a widespread problem in events.ts
68- await publish (
69- formatErrors ( validate . errors || [ ] , body ) . map ( ( error ) => ( {
70- schema : hydroNames . validation ,
71- value : error ,
72- } ) ) ,
73- )
66+ if ( body . context ) {
67+ // Add dotcom_user to the context if it's available
68+ // JSON.stringify removes `undefined` values but not `null`, and we don't want to send `null` to Hydro
69+ body . context . dotcom_user = req . cookies ?. dotcom_user ? req . cookies . dotcom_user : undefined
70+ body . context . is_staff = Boolean ( req . cookies ?. staffonly )
71+ }
72+ const validate = validators [ type ]
73+ if ( ! validate ( body ) ) {
74+ validationErrors . push ( {
75+ event : body ,
76+ error : validate . errors || [ ] ,
77+ } )
78+ // This protects so we don't bother sending the same validation
79+ // error, per user, more than once (per time interval).
80+ // This helps if we're bombarded with junk bot traffic. So it
81+ // protects our Hydro instance from being overloaded with things
82+ // that aren't helping anybody.
83+ const hash = `${ req . ip } :${ ( validate . errors || [ ] )
84+ . map ( ( error : ErrorObject ) => error . message + error . instancePath )
85+ . join ( ':' ) } `
86+ if ( ! sentValidationErrors . has ( hash ) ) {
87+ sentValidationErrors . set ( hash , true )
88+ formatErrors ( validate . errors || [ ] , body ) . map ( ( error ) => {
89+ validationErrors . push ( { schema : hydroNames . validation , value : error } )
90+ } )
91+ }
92+ continue
93+ }
94+ validEvents . push ( {
95+ schema : hydroNames [ type ] ,
96+ value : omit ( body , OMIT_FIELDS ) ,
97+ } )
98+ } catch ( eventError ) {
99+ console . error ( 'Error validating event:' , eventError )
74100 }
75- // We aren't helping bots spam us :)
76- return res . status ( 400 ) . json ( isProd ? { } : validate . errors )
77101 }
78-
79- if ( isSurvey ( body ) && body . survey_comment ) {
80- body . survey_rating = await getSurveyCommentRating ( {
81- comment : body . survey_comment ,
82- language : body . context . path_language || 'en' ,
83- } )
84- body . survey_comment_language = await getGuessedLanguage ( body . survey_comment )
102+ if ( validEvents . length > 0 ) {
103+ await publish ( validEvents )
85104 }
86105
87- // Add dotcom_user to the context if it's available
88- // JSON.stringify removes `undefined` values but not `null`, and we don't want to send `null` to Hydro
89- if ( body . context ) {
90- body . context . dotcom_user = req . cookies ?. dotcom_user ? req . cookies . dotcom_user : undefined
91- // Add if the user is a staff, using the 'staffonly' cookie
92- body . context . is_staff = Boolean ( req . cookies ?. staffonly )
106+ if ( validationErrors . length > 0 ) {
107+ await publish ( validationErrors )
93108 }
109+ const statusCode = validationErrors . length > 0 ? 400 : 200
94110
95- await publish ( {
96- schema : hydroNames [ type ] ,
97- value : omit ( body , OMIT_FIELDS ) ,
98- } )
99-
100- return res . json ( { } )
111+ return res . status ( statusCode ) . json (
112+ isProd
113+ ? undefined
114+ : {
115+ success_count : validEvents . length ,
116+ failure_count : validationErrors . length ,
117+ details : validationErrors ,
118+ } ,
119+ )
101120 } ) ,
102121)
103122
0 commit comments