Implement feedback as a JSON API, fix missing await on all post_request() calls
authorNick Downing <nick@ndcode.org>
Sun, 16 Jan 2022 02:02:39 +0000 (13:02 +1100)
committerNick Downing <nick@ndcode.org>
Sun, 16 Jan 2022 02:02:39 +0000 (13:02 +1100)
14 files changed:
_config/globals.json
_config/nodemailer_feedback.json
_lib/navbar.jst
api/feedback.html.jst [deleted file]
api/feedback.json.jst [new file with mode: 0644]
api/password_reset.json.jst
api/sign_in.json.jst
api/sign_out.json.jst
api/sign_up/create_account.json.jst
api/sign_up/get_draft.json.jst
api/sign_up/send_email_verification_link.json.jst
api/sign_up/set_draft.json.jst
api/sign_up/verify_email.json.jst
api/verify_password.json.jst

index 4b5779c..798870f 100644 (file)
@@ -1,9 +1,9 @@
 {
   "site_url": "https://www.ndcode.org",
   "site_title": "NDCODE",
-  "contact_from": "NDCODE Contact <contact@ndcode.org>",
+  "contact_from": "NDCODE contact <contact@ndcode.org>",
   "contact_to": "Nick Downing <nick@ndcode.org>",
-  "feedback_from": "NDCODE Feedback <feedback@ndcode.org>",
+  "feedback_from": "NDCODE feedback <feedback@ndcode.org>",
   "feedback_to": "Nick Downing <nick@ndcode.org>",
   "noreply_from": "NDCODE <noreply@ndcode.org>",
   "noreply_signature": "NDCODE Team",
index 0dd697f..675f676 100644 (file)
@@ -4,7 +4,7 @@
   "secure": false,
   "auth": {
     "user": "feedback@ndcode.org",
-    "pass": "XXXFeedback12",
+    "pass": "XXXFeedback12"
   },
   "requireTLS": true
 }
index 4e80d95..a993c46 100644 (file)
@@ -253,21 +253,8 @@ return async (env, head, body, scripts) => {
               div.row {
                 div.col-md-12 {
                   div.form-group {
-                    label(for="feedback-message") {'Message *'}
-                    textarea.form-control#feedback-message(placeholder="Please tell us your thoughts" rows="4" required="required" data-error="Please, leave us a message.") {}
-                    div.help-block.with-errors {}
-                  }
-                }
-              }
-              p {} // fix this later
-              div.row {
-                div.col-md-12 {
-                  p.text-muted {
-                    strong {'*'}
-                    'These fields are required.'
-                    //'Contact form template by '
-                    //a(href="https://bootstrapious.com/p/how-to-build-a-working-bootstrap-feedback-form" target="_blank") {'Bootstrapious'}
-                    //'.'
+                    label.form-label(for="feedback-message") {'Message'}
+                    textarea.form-control#feedback-message(placeholder="Please tell us your thoughts" rows="4" required="required") {}
                   }
                 }
               }
@@ -314,6 +301,10 @@ return async (env, head, body, scripts) => {
           '/api/sign_out.json',
           ...arguments
         )
+        let feedback = async (...arguments) => api_call(
+          '/api/feedback.json',
+          ...arguments
+        )
 
         // this function can be overridden in a further script
         function sign_in_out(status) {
@@ -442,28 +433,35 @@ return async (env, head, body, scripts) => {
 
             document.getElementById('feedback-submit').addEventListener(
               'click',
-              () => {
-                $.ajax(
-                  {
-                    url: '/api/feedback.html',
-                    type: 'POST',
-                    data: {
-                      page: window.location.href,
-                      message: $('#feedback-message').val()
-                    },
-                    success: (data, textStatus, jqXHR) => {
-                      $('#feedback-modal').modal('hide')
-                      document.getElementById('message-modal-message').textContent = data
-                      $('#message-modal-message').text(data)
-                      $('#message-modal').modal('show')
-                    },
-                    error: (jqXHR, textStatus, errorThrown) => {
-                      $('#feedback-modal').modal('hide')
-                      document.getElementById('message-modal-message').textContent = errorThrown
-                      $('#message-modal').modal('show')
-                    }
-                  }
-                )
+              async () => {
+                try {
+                  await feedback(
+                    location.href,
+                    document.getElementById('feedback-message').value.slice(0, 65536)
+                  )
+                }
+                catch (error) {
+                  let problem =
+                    error instanceof Problem ?
+                      error :
+                      new Problem(
+                        // title
+                        'Bad request',
+                        // details
+                        (error.stack || error.message).toString()
+                        // status
+                        400
+                      )
+
+                  document.getElementById('message-modal-message').textContent = problem.detail
+                  $('#feedback-modal').modal('hide')
+                  $('#message-modal').modal('show')
+                  return
+                }
+
+                document.getElementById('message-modal-message').textContent = 'Thanks! We have received your feedback.'
+                $('#feedback-modal').modal('hide')
+                $('#message-modal').modal('show')
               }
             )
           }
diff --git a/api/feedback.html.jst b/api/feedback.html.jst
deleted file mode 100644 (file)
index b67836b..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-let querystring = require('querystring')
-let stream_buffers = require('stream-buffers')
-let XDate = require('xdate')
-
-return async env => {
-  let globals = await env.site.get_json('/_config/globals.json')
-  let nodemailer_feedback = await env.site.get_nodemailer(
-    '/_config/email_feedback.json'
-  )
-
-  let message
-  if (env.request.method === 'POST') {
-    let write_stream = new stream_buffers.WritableStreamBuffer()
-    let data = new Promise(
-      (resolve, reject) => {
-        write_stream.
-        on('finish', () => {resolve(write_stream.getContents())}).
-        on('error', () => {reject()})
-      }
-    )
-    env.request.pipe(write_stream)
-    let query = querystring.parse((await data).toString())
-    console.log('received feedback form:', query.page)
-
-    // save the form contents in a dated logfile, so that we can
-    // recover manually if the email doesn't send for some reason
-    date = new XDate()
-    query.date = date.toUTCString()
-
-    let transaction = await env.site.database.Transaction()
-    ;(
-      await (
-        await (
-          await (
-            await transaction.get({})
-          ).get('logs', {})
-        ).get(date.toUTCString('yyyyMMdd'), {})
-      ).get('feedback', [])
-    ).push(transaction.json_to_logjson(query))
-    transaction.commit()
-
-    // send email (asynchronously)
-    nodemailer_feedback.sendMail(
-      {
-        from: globals.feedback_from,
-        to: globals.feedback_to,
-        subject: 'Page: ' + query.page,
-        text: query.message,
-      },
-      (err, message) => {
-        if (err)
-          console.error(err.stack || err.message)
-        else
-          console.log('sent feedback email:', query.page)
-      }
-    )
-
-    message = 'Thanks!'
-  }
-  else
-    message = 'Please POST.'
-
-  env.site.serve(env, 200, Buffer.from(message), 'feedback.html.jst')
-}
diff --git a/api/feedback.json.jst b/api/feedback.json.jst
new file mode 100644 (file)
index 0000000..860f3e2
--- /dev/null
@@ -0,0 +1,50 @@
+let logjson = (await import('@ndcode/logjson')).default
+let XDate = require('xdate')
+
+return async env => {
+  let globals = await env.site.get_json('/_config/globals.json')
+  let nodemailer_feedback = await env.site.get_nodemailer(
+    '/_config/nodemailer_feedback.json'
+  )
+  let post_request = await _require('/_lib/post_request.jst')
+  let session_cookie = await _require('/_lib/session_cookie.jst')
+
+  await post_request(
+    // env
+    env,
+    // endpoint
+    '/api/feedback.json',
+    // handler
+    async (page, message) => {
+      // coerce and/or validate
+      page = page.slice(0, 256)
+      mesage = message.slice(0, 65536)
+      if (page.length === 0 || message.length === 0)
+        throw new Problem(
+          'Bad request',
+          'Minimum length check failed',
+          400
+        )
+
+      let transaction = await env.site.database.Transaction()
+      try {
+        // initialize env.session_key, set cookie in env.response
+        await session_cookie(env, transaction)
+        await transaction.commit()
+      }
+      catch (error) {
+        transaction.rollback()
+        throw error
+      }
+
+      await nodemailer_feedback.sendMail(
+        {
+          from: globals.feedback_from,
+          to: globals.feedback_to,
+          subject: 'Page: ' + page,
+          text: message
+        }
+      )
+    }
+  )
+}
index 6f778fc..b439a17 100644 (file)
@@ -11,7 +11,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint
index 30f8564..bf3b02e 100644 (file)
@@ -9,7 +9,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint
index 768de0e..264fb3c 100644 (file)
@@ -9,7 +9,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint
index 5789c6a..bb96f5c 100644 (file)
@@ -5,7 +5,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint
index 6bdb55a..44f20f0 100644 (file)
@@ -6,7 +6,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint
index 92a77f6..08f0db6 100644 (file)
@@ -11,7 +11,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint
index 3b3c71b..e7d2457 100644 (file)
@@ -5,7 +5,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint
index d7f915c..8ff628a 100644 (file)
@@ -7,7 +7,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint
index 3e58569..bb36a53 100644 (file)
@@ -7,7 +7,7 @@ return async env => {
   let session_cookie = await _require('/_lib/session_cookie.jst')
   let Problem = await _require('/_lib/Problem.jst')
 
-  post_request(
+  await post_request(
     // env
     env,
     // endpoint