Sunday, 29 December 2024

Not My Job, Not My Problem

"Not my job." These three words are often considered bad form when uttered by an employee; but are they, really? As always, context is king.

Employers seem to think that within the confines of their company, their word is law, and therefore anything they assign to you is automatically your job regardless of how far it deviates from the actual job description. In many cases, there's even literally a clause that says (to that effect) that the employee's duties include anything else not listed in the description that their supervisor may choose to assign to them.

Nobody respects a doormat.

It can't be said enough, that being seen as the office doormat is the mother of all bad ideas. In both the big and small picture, there is absolutely nothing in it for you.

On the other hand, the stereotypical Singaporean has this famous attitude of not wanting to lose even in petty ways. This attribute of missing the forest for the trees is exactly what stops people from becoming all they could be. Singaporean employers want to milk as much value out of their employees as possible, and Singaporean employees don't want to give away extra work for free. How does one resolve such a contentious relationship?

The case for taking on extra duties

Occasionally venturing outside the confines of your job scope can be a good thing. You pick up valuable experience and you earn a reputation as a team player. If you save your employer the trouble of hiring someone to do a job that you can take care of in a jiffy (and doesn't interfere too much with your actual job), that's automatic justification for your continued employment.

How did I initially get experience in DevOps? By volunteering for the task when a colleague left the company. I could have said I had no bandwidth for extra tasks and left it at that, but it was an opportunity to pick up new stuff. Plus, I was pretty sure at some point they were going to finger me for the role anyway, so I might as well get it over with, amirite?

Also, if you do this enough, people generally tend to cut you some slack for minor infractions. Show up thirty minutes late three days in a row? Miss a deadline? Take too many smoke breaks? Hey, what can people reasonably expect when they continually pile new things on your plate? What are they gonna do; fire you and coax some other poor sucker into taking over your workload? I'm not saying that won't happen; I'm saying that people are generally smarter than that.

Too many smoke breaks?

Automatically saying "not my job" regardless of context, even if you're technically right, makes you look like a pain in the ass to work with. Now, if you're extremely good at your job and not readily replaceable, you can reasonably expect to get away with it. Otherwise, drop the attitude or find new employment.

Do I sound like a corporate shill yet? Be patient; I'm about to get to the cases against doing extra work.

The case against

If you're too agreeable and constantly take on new unrelated tasks, people are going to see you as their go-to option for dumping stuff on. They no longer see that thing they asked you to do, as a favor. It has become your job. Now if you were being paid for that extra workload, that would be a different story. But chances are, you're not. That extra work doesn't get factored into your performance appraisals when it comes to determining raises or promotions. And if enough jackasses are in Management (sometimes just one is enough!) that extra work may not even count as a mitigating factor for the occasional infraction.

It's also counterintuitive to do someone else's work for them. This robs them of the opportunity to do the work themselves, and possibly learn from it. Also, you don't get paid for it, and your actual work suffers.

There were times when I received data that needed to be processed by the system. And during those times, I noticed some typos. Spelling and grammatical in the data that would still make them processable without compromising data integrity, but could potentially be embarrassing if the customer spotted them.

Out of habit, I sometimes quietly corrected those errors, and moved on. That was in my first year on the job. By the third year, I had come to an entirely different conclusion. I work in software development, not marketing. In terms of pay grade or job description, correcting elementary grammatical errors is not my job at all. If people can't be bothered to run their work through a spell-check, well, they're not my kids and I'm certainly not their babysitter. Also, by that time, the number of things on my plate had grown to such an alarming extent that I doubt my boss would have approved if he'd suspected I spent even a few minutes a day correcting typos. They didn't need me to do that job. They needed to hire better people.

I've said before that taking on new stuff might be a good way to level up and get out of your comfort zone. But what if the task at hand is not something you can afford to screw up? Like flying a plane, or disarming a time bomb. Basically something that you aren't qualified or experienced enough to do, and has serious negative consequences for failure.

One idiot boss, one
broken table.

I once worked at a company where a colleague was asked to move a table from one room to another. It wasn't in his job scope, and he did his best, but somehow the flimsy table collapsed. Partly it was due to mishandling, but to be fair, he just wasn't trained to do this stuff.

Our boss lamented to me that this guy had been underperforming and he was giving him a chance to be useful, but he had screwed even that up. This is an unfortunate tendency of some employers - no matter how unreasonable or illogical the expectation, they somehow think they're doing you a favor by holding you to it, or giving you some grand opportunity. Honestly, if the employee had injured himself in the process of carrying out that task, I'm pretty sure a lawsuit would be both tenable and well-deserved.

Rule of thumb: will those tasks interfere with your actual job? Then the default response is "no", unless you have documentation in black-and-white. To that end, if someone starts asking for favors too often, insist that they formally send you that request in an email that's CC-ed to your boss, to HR, to whomever else you deem appropriate. This accomplishes two objectives. Firstly, the appropriate people have visibility over what you're being asked to do or at least have no plausible deniability. Second, people are usually asking you to do shit for the sake of their convenience. If it stops being that convenient, they'll stop doing it. If their requests draw negative attention from people further up the pecking order, they'll even actively avoid doing it.

People in the workplace are generally driven by self-interest. No one is working for any significant reason beyond earning a paycheck. Learn to exploit that, and you should be fine.

The most important point, at least in my view

You've heard all the arguments I have against doing someone else's job for them. But these are the biggest ones, and I've saved them for last. Here they are: professional responsibility, and organizational process.

In any organization, there are systems and processes in place for a reason. Certain people are put in positions of responsibility, also for good reason.

Surely having more people work on a task can only be a good thing? More is not always merrier, and I'm about to explain why, using a very simplistic example.

Say for instance, I'm in charge of a database management system. Any changes, any data that goes in or out of it, is my responsibility. I have to know. And then my colleague, who has certain permissions in the system, spots what he thinks is a mistake in the configuration and decides to rectify it on the spot. But since he's doing what he sees as extra work (which is always good, right?) therefore subconsciously he neglects due process, which is to let me know when doing stuff like that.

The configuration results in some behavioral changes in other systems that connect to this database. It's a while before things get traced back here, to the database management system which I'm in charge of. To my horror, I don't even remember making the changes that resulted in the erratic system behavior. Why's that? Because I didn't make those changes. But here's the kicker - I'm responsible for them anyway.

My hypothetical colleague, thinking he was being helpful and thinking his extra effort would help the company, neglected due process and cowboy-copped his way through the system configuration. And because the database management system wasn't his professional responsibility, he didn't see the need to follow up. With the very best of intentions, he screwed the pooch because he introduced configuration changes into a system that he wasn't in charge of, and failed to notify those who were in charge of it.

Now I know what most people would be thinking at this point - wouldn't there be safeguards in place to prevent this sort of thing from happening? Yes, that would be correct, which is why, thankfully, the example I just gave you is only hypothetical. But the principle remains the same. You can't attempt to "help", thinking that it's all good because you're doing more than you're paid to do, and interfering with people who are actually in charge of the job you're trying to help with. People who have to be responsible for the results. Not you.

You're not running
a lemonade stand.

Organizational process may not be very important if you're, say, running a lemonade stand. Unfortunately, for the vast majority of workplaces, the systems are far more delicate, the consequences far more dire, than that of a lemonade stand.

So yeah. Professional responsibility and organizational process. Before trying to do extra, make sure you're accountable to the people who actually matter. If your unsolicited helpfulness results in man days lost for your co-workers, I doubt they'll be feeling very grateful.

Final words

There's always a little give-and-take in a relationship, especially a professional one. By all means, do extra. But never be shy about letting your colleagues know that they're treading a fine and dangerous line. In other words, be obliging, but don't let your goodwill be taken for granted.

And remember, doing more work is not always better - for you or your employer. 


Your problematic programmer,
T___T

Monday, 23 December 2024

Web Tutorial: NodeJS Christmas Mailer (Part 3/3)

Time to do some mailing! We'll need to install the nodemailer package like so.
install --save nodemailer


This package handles transport for SMTP. We'll only require nodemailer during this route.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");
  
  console.log(req.body);
  res.redirect(303, "/thankyou");
});


Once we've assigned the resultant object to the variable nodemailer, we call the createTransport() method and assign the result to transport.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport();

  console.log(req.body);
  res.redirect(303, "/thankyou");
});


When you run createTransport(), you'll need to pass in a JSON object with the following properties. Inside it, auth is an object as well with user and pass as properties.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport({
    service: ,
    host: ,
    secure: ,
    port: ,
    auth: {
      user: ,
      pass:
    }
  }
);

  console.log(req.body);
  res.redirect(303, "/thankyou");
});


I'll be using Google's Gmail for this. If you'r also using Gmail, the service, host and port properties are as follows. You need to look up the values for any other service you'd like to use. secure will be set to true in most (if not all) cases.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport({
    service: "Gmail",
    host: "smtp.gmail.com",
    secure: true,
    port: 465,
    auth: {
      user: ,
      pass:
    }
  });

  console.log(req.body);
  res.redirect(303, "/thankyou");
});


For auth, we'll use these values. Remember how we set the secret in the auth.js file? Well, we'll be doing something similar here.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport({
    service: "Gmail",
    host: "smtp.gmail.com",
    secure: true,
    port: 465,
    auth: {
      user: auth.mail.user,
      pass: auth.mail.password
    }
  });

  console.log(req.body);
  res.redirect(303, "/thankyou");
});


In here, I have the mail object with user and password as properties. user is the email address I want to use, and password is the app password I obtained for this. Here's the procedure you need to follow for a Gmail account. The email service you eventually choose may have its own procedure.

auth.js
module.exports = {
  secret: "thisisasecret",
  mail: {
    user: "teochewthunder@gmail.com",
    password: "xxxx xxxx xxxx xxxx"
  }

}


Now create the options object. The properties from and to are email addresses. from will be the email address you send from, so I use my own email address. to is the email address specified n the form submitted by the user.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport({
    service: "Gmail",
    host: "smtp.gmail.com",
    secure: true,
    port: 465,
    auth: {
      user: auth.mail.user,
      pass: auth.mail.password
    }
  });

  var options = {
    from: "teochwthunder@gmail.com",
    to: req.body.txtEmail,
    subject: ,
    text:
  };


  console.log(req.body);
  res.redirect(303, "/thankyou");
});


subject can be any text. I'm just going to keep it simple. And text is the content specified in the form.

app.js
var options = {
  from: "teochwthunder@gmail.com",
  to: req.body.txtEmail,
  subject: "A Christmas message from your friend has arrived!",
  text: req.body.txtMessage
};


After that, we use the sendMail() method of the transport object. The first argument is options, since this tells nodemailer the particulars of who and what to send. The second is a callback with the parameter error.

app.js
var options = {
  from: "teochwthunder@gmail.com",
  to: req.body.txtEmail,
  subject: "A Christmas message from your friend has arrived!",
  text: req.body.txtMessage
};

transport.sendMail(options, (error) => {

});  


console.log(req.body);
res.redirect(303, "/thankyou");


If error exists, we handle it the way we've handled code 500 errors. Otherwise, we redirect to the thankyou route. Which also means we will no longer be needing the code below that, so comment it off.

app.js
transport.sendMail(options, (error) => {
  if (error) {
    res.render("500", { errorMessage: error.code });
  } else {
    res.redirect(303, "/thankyou");
  }
});  

console.log(req.body);
//res.redirect(303, "/thankyou");


Let's try this.


This is the email!


HTML Email

Let's take this a step further. We've already nailed down the process of sending email. Now, we want to render the email in HTML. For this, we create a layout, emailtemplate.handlebars. This has a table-based layout and inline CSS - really retro stuff because that's the safest way in email browsers. Note that the style attributes have been left as blank strings.

views/emailtemplate.handlebars
<table border="0" width="500px" cellpadding="5" cellspacing="0" style="font-size:14px;text-align: center;">
  <tr>
    <td colspan = "2" style="">
      <h1 style="">MERRY CHRISTMAS!</h1>
    </td>
  </tr>

  <tr>
    <td style="" width="70%">
      <p style=""></p>
    </td>
    <td style="">
      <table cellpadding="5" cellspacing="0">
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="">&#9733;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="">&#9679;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style=""></td>
        </tr>
        <tr>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="">&nbsp;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>        
      </table>
    </td>
  </tr>
  <tr>
    <td colspan = "2" style="">
      <p style="">- Teochew Thunder</p>
    </td>
  </tr>
</table>


Now let's add placeholders for content and variables such as colors0, colors1, colors2, colors3, colors4.

views/emailtemplate.handlebars
<table border="0" width="500px" cellpadding="5" cellspacing="0" style="font-size:14px;text-align: center;">
  <tr>
    <td colspan = "2" style="background-color:{{ colors0 }};">
      <h1 style="color:{{ colors4 }};">MERRY CHRISTMAS!</h1>
    </td>
  </tr>

  <tr>
    <td style="background-color:{{ colors1 }};" width="70%">
      <p style="color:{{ colors4 }};">{{{ message }}}</p>
    </td>
    <td style="background-color:{{ colors4 }};text-align:center;">
      <table cellpadding="5" cellspacing="0">
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="color:#FFFF00;">&#9733;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors3 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style=""></td>
        </tr>
        <tr>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors3 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors3 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="background-color:{{ colors0 }};">&nbsp;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>        
      </table>
    </td>
  </tr>
  <tr>
    <td colspan = "2" style="background-color:{{ colors0 }};text-align: right;">
      <p style="color:{{ colors4 }};">- Teochew Thunder</p>
    </td>
  </tr>
</table>


Back to the process route, we define layoutColors as an object with three properties - red, green and blue. Each is an array containing hex values. Basically they are shades of red, green and blue.

app.js
var transport = nodemailer.createTransport({
  service: "Gmail",
  host: "smtp.gmail.com",
  secure: true,
  port: 465,
  auth: {
    user: auth.mail.user,
    pass: auth.mail.password
  }
});

var layoutColors = {
  red: ["#440000", "#AA0000", "#FF0000", "#FFAAAA", "#FFCCCC"],
  green: ["#004400", "#00AA00", "#00FF00", "#AAFFAA", "#CCFFCC"],
  blue: ["#000044", "#0000AA", "#0000FF", "#AAAAFF", "#CCCCFF"],
};


var options = {
  from: "teochwthunder@gmail.com",
  to: req.body.txtEmail,
  subject: "A Christmas message from your friend has arrived!",
  text: req.body.txtMessage
};


We then use render() to create the HTML using the template we defined earlier, with an object that we will fill in soon. After rendering, we have a callback, where html is the HTML rendered. In it, we first check if error exists, and handle it if so. Otherwise, we go to the code we wrote earlier.

app.js
var layoutColors = {
  red: ["#440000", "#AA0000", "#FF0000", "#FFAAAA", "#FFCCCC"],
  green: ["#004400", "#00AA00", "#00FF00", "#AAFFAA", "#CCFFCC"],
  blue: ["#000044", "#0000AA", "#0000FF", "#AAAAFF", "#CCCCFF"],
};

res.render("emailtemplate", {}, (error, html) => {
  if (error) {
    console.log(error);
    res.render("500", { errorMessage: error.code });
  } else {

    var options = {
      from: "teochwthunder@gmail.com",
      to: req.body.txtEmail,
      subject: "A Christmas message from your friend has arrived!",
      text: req.body.txtMessage
    };
  
    transport.sendMail(options, (error) => {
      if (error) {
        console.log(error);
        res.render("500", { errorMessage: error.code });
      } else {
        res.redirect(303, "/thankyou");
      }
    });
  }  
});


Then we fill in the object that will populate values in the template. Layout is null, because there is no layout - we're using emailtemplates.handlebars by itself. We determine colors0, colors1, colors2, colors3 and colors4 based on the value of the drop-down list submitted in the form. And message is the value of txtMessage, with the line breaks replaced with HTML breaks.

app.js
var layoutColors = {
  red: ["#440000", "#AA0000", "#FF0000", "#FFAAAA", "#FFCCCC"],
  green: ["#004400", "#00AA00", "#00FF00", "#AAFFAA", "#CCFFCC"],
  blue: ["#000044", "#0000AA", "#0000FF", "#AAAAFF", "#CCCCFF"],
};

res.render("emailtemplate", {
  layout: null,
  colors0: layoutColors[req.body.ddlLayout][0],
  colors1: layoutColors[req.body.ddlLayout][1],
  colors2: layoutColors[req.body.ddlLayout][2],
  colors3: layoutColors[req.body.ddlLayout][3],
  colors4: layoutColors[req.body.ddlLayout][4],
  message: req.body.txtMessage.split("\n").join("<br />")

}, (error, html) => {
  if (error) {
    console.log(error);
    res.render("500", { errorMessage: error.code });
  } else {
    var options = {
      from: "teochwthunder@gmail.com",
      to: req.body.txtEmail,
      subject: "A Christmas message from your friend has arrived!",
      text: req.body.txtMessage
    };
  
    transport.sendMail(options, (error) => {
      if (error) {
        console.log(error);
        res.render("500", { errorMessage: error.code });
      } else {
        res.redirect(303, "/thankyou");
      }
    });
  }
});


Except that the mailing code now has HTML instead of text. And we use the html value instead of the text value of txtMessage.

app.js
var options = {
  from: "teochwthunder@gmail.com",
  to: req.body.txtEmail,
  subject: "A Christmas message from your friend has arrived!",
  html: html
};


Now let's send the form again.


Try the green layout.


And the blue.


Merry Christmas!

This wasn't my fanciest work, I'm afraid. Just wanted to feel my way around NodeJS. But we all have to start somewhere!

Try Nodemailer today. Yule love it!
T___T

Friday, 20 December 2024

Web Tutorial: NodeJS Christmas Mailer (Part 2/3)

All-righty then!

We have a form and now we're going to handle it. Remember the form will use the process route? Well, let's create that. Note that this is a POST.

app.js
app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});

app.post("/process", (req, res)=> {

}


app.use((req, res, next)=> {
  res.status(404);
  res.render("404");
});


But if we want to grab the values of the form, we have to install another module. I use this one, but it might be deprecated at this point, so if you can find something better, go with that.
install --save body-parser


We'll need to include this. We will use the middleware function use() and pass in the module body-parser.

app.js
app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);

app.use(require("body-parser")());

app.get("/", (req, res)=> {
  res.render("form");
});


Back to the process route. Log the body object from req.

app.js
app.post("/process", (req, res)=> {
  console.log(req.body);
});


Then redirect to the thankyou route with a Status of 303. We use 303 to prevent the repeated submission of form data if page is refreshed.

app.js
app.post("/process", (req, res)=> {
  console.log(req.body);
  res.redirect(303, "/thankyou");
});


Now to test this. Fill up the form.


You should see this in the CLI! This will be information you can use.

Also, see what happens when you try to submit the form without filling stuff in. HTML5 does its thing. Let's use this as an excuse not to spend time validating the fields on the server. Just for today.


Implementing anti-CSRF

This is not mandatory to ensure that your form works, but it's pretty damn important, anyway. An anti-CSRF token is a mechanism to ensure that the request comes from a legitimate source. Probably overkill for this form, but let's see how it's done.

We'll use the csurf module.
install --save csurf


But an anti-CSRF token also needs cookies and sessions. So we'll need to install these too.
install --save express-session
install --save cookie-parser


Add in these modules.

app.js
app.use(require("body-parser")());
app.use(require("cookie-parser")());
app.use(require("express-session")());
app.use(require("csurf")());


app.get("/", (req, res)=> {
  res.render("form");
});


When you try to run the app again, you'll get this error. That's because the cookie-parser module requires a secret.


Actually, even a string containing only a single space, qualifies as a secret. This will suffice to get things working again. But that's not a good way to do things.

app.js
app.use(require("cookie-parser")(" "));


What we should do, is create a file separate from app.js. Let's call it auth.js. In it, we will declare that this file exports an object. This object has a property, secret. Here, just use any string you like as its value.

auth.js
module.exports = {
  secret: "thisisasecret"
}


Then import auth.js using the require() function, and set the result to the variable auth.

app.js
var auth = require("./auth.js");
var express = require("express");

var app = express();


And here, instead of the string we used earlier, we have the secret property of auth.
app.js
app.use(require("cookie-parser")(auth.secret));


Now things should run! However, when you attempt to submit the form, you should get this error. That's because once you include the module csurf, you're expected to have the anti-CSRF token as part of the form, and we haven't done that yet.


Before we do that, however, let's tidy up one loose end. Remember the last two errors you've seen was ugly black text and white backgrounds? Let's change that. Add another middleware function after the one we used to handle 404s. This one has the err parameter in the callback. So if the URL is valid but an error is thrown, the status is set to 500 and we render the page 500.

app.js
app.use((req, res, next)=> {
  res.status(404);
  res.render("404");
});

app.use((err, req, res, next)=> {
  res.status(500);
  res.render("500");
});


app.listen(app.get("port"), ()=> {

});


But we also want to pass in information, so let's use the code property of err, and set it as the value of errorMessage which we will pass into the page.

app.js
app.use(function(err, req, res, next) {
  res.status(500);
  res.render("500", { errorMessage: err.code });
});


In here, you'll see where we've placed errorMessage.

views/500.handlebars
<h1>500</h1>

<p>There was an error.</p>
<p><b>{{ errorMessage }}</b></p>


Now run the code again! There's still an error, but we've beautified it.


OK, let's now create the anti-CSRF token. Add this object in the call to render(), as a second argument. It will have the property _csrf and the value is the value returned by calling the csrfToken() method of the req object.

app.js
app.get("/", (req, res)=> {
  res.render("form", { csrf: req.csrfToken() });
});


Now in the view form, add a text field for that value. The name of the field is _csrf.

views/form.handlebars
<form action="/process" method="POST">
  <input type="text" name="_csrf" value="{{ csrf }}" />
  <label for="ddlLayout">
    <span class="labelText">LAYOUT</span>
    <select id="ddlLayout" name="ddlLayout">
      <option value="red">RED</option>
      <option value="green">GREEN</option>
      <option value="blue">BLUE</option>
    </select>
  </label>


We should see the value in there!


Change the type attribute to hidden, because while we want the element in the form, we don't want it to be seen.

views/form.handlebars
<input type="hidden" name="_csrf" value="{{ csrf }}" />


Try submitting the form again and this time it should work.

And we're done here. What we want to do next, is use the values submitted via the form, to send a HTML email!

Next

Mailing and HTML emails.

Tuesday, 17 December 2024

Web Tutorial: NodeJS Christmas Mailer (Part 1/3)

Tis the season to be jolly, readers! In the spirit of learning, we'll be exploring a bit of NodeJS today. I'm a bit of a beginner here myself, so bear with me!

What we'll do here, is create a web form that will send a HTML email. Sounds simple, and it is! However, that comes with a few considerations. So buckle up; this sleigh is going on a ride.

You should already have a Node environment, and set up the folder in which you'll be working. To begin, we install Express because it will take care of the tedious and repetitive scaffolding.
install --save express


Now create this file. This will be the file which we run whenever we need to restart the script. We need to run the require() function with "express" as an argument, then assign the returned value (which is another function) to the variable express.

app.js
var express = require("express");


Then we run the newly-created function express() and assign the value to app.

app.js
var express = require("express");

var app = express();


Next, we'll install Handlebars as a templating engine. It's not absolutely necessary, of course, but knowing how to use a templating engine is a good basic skill. Run this in the command line.
install --save express-handlebars


Again, we need to require() handlebars. Assign the returned value to the variable handlebars.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars");


Then chain this to use the method create(). In it, we pass an object with one single property - defaultLayout with a value of "main".

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout:"main"});


Then, by using app's engine() method, we set the app to use the Handlebars engine (represented by the engine property of the handlebars object) and map the ".handlebars" extension to that engine.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout:"main"});
app.engine("handlebars", handlebars.engine);


Now let's set some stuff. Use the set() method of the app object, to set the view engine to handlebars.

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout:"main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");


We also want to set the port. In this case, we use the core process object's PORT property (within the sub-object env) or 3000 if it doesn't exist (as can be the case in a different environment).

app.js
var express = require("express");

var app = express();

var handlebars = require("express-handlebars").create({defaultLayout:"main"});
app.engine("handlebars", handlebars.engine);

app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);


After that, we'll want to do routes. Here's our first route, and it's the most basic one. It's a GET request and it's the home address. Just a slash, with nothing after it. The second argument in the get() method is a callback. The callback uses the parameters req and res to represent the request and response.

app.js
app.set("view engine", "handlebars");
app.set("port", process.env.PORT || 3000);

app.get("/", (req, res)=> {

});


In the callback, we use the response's render() method, and pass in "form" as the argument. This means that for the home page, we want to render the form page.

app.js
app.get("/", (req, res)=> {
  res.render("form");
});


Finally, we set the app to listen at the port we set earlier. We don't have to add any more statements to the callback, but it's useful for if we need to do any troubleshooting.

app.js
app.get("/", (req, res)=> {
  res.render("form");
});

app.listen(app.get("port"), ()=> {
  
});


Remember that the default layout is main and the home page is form? Our next steps will bear this in mind. In Handlebars, these files are in the views directory by default. And layouts are stored in the layouts directory of the views directory. Let's just start with form. Create form.handlebars in views. We'll start simple, with a h1 tag and a paragraph.

views/form.handlebars
<h1>Welcome to Teochew Thunder's Xmas Mailer.</h1>
<p>Email a friend!</p>


Now let's do the layout. Create the layouts directory within views, and then create main.handlebars in that sub-directory. It's a bare-bones HTML boilerplate.

views/layouts/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xmas Mailer</title>
  </head>
  <body>

  </body>
</html>


We will add the template string that brings body into the body tag. body is a keyword in the templating engine that represents the HTML content. And in this case, the HTML content will be inside main.handlebars.

views/layouts/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xmas Mailer</title>
  </head>
  <body>
    {{{ body }}}
  </body>
</html>


You'll see this in your browser at port 3000!


Time to add some styling. Generally there's a better way to do this, but I'm just going to put all the styling in the main layout.

views/layouts/main.handlebars
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xmas Mailer</title>

    <style>
      body {
        font-family: verdana, sans-serif;
        font-size: 14px;
        background-color: rgba(255, 200, 0, 0.5);
        color: rgba(255, 100, 0, 0.8);
      }

      h1, p {
        text-align: center;
      }
    </style>

  </head>
  <body>
    {{{ body }}}
  </body>
</html>


Here we are, styling applied.


We'll need a few more! Add the "thank you" route. It's a GET, and renders the thankyou page.

app.js
app.get("/", (req, res)=> {
  res.render("form");
});

app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});


app.listen(app.get("port"), ()=> {

});


Create this file in the views directory. It has a h1 tag, two paragraphs and a link.

views/thankyou.handlebars
<h1>Thank you!</h1>
<p>Your mail has been sent.</p>
<p><a href="./">Send another.</a></p>


Now try this URL! If you click on the link, you should go back to the main page.


We'll create routes to handle errors as well. After the two routes we've defined so far, add a middleware function. It's the use() method of the app object. Again, it has req and res, and also next. But we'll only need res.

app.js
app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});

app.use((req, res, next)=> {

});


app.listen(app.get("port"), ()=> {

});


We set the status to 404 here. Bear in mind that this code only executes if none of the other routes are triggered.

app.js
app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});

app.use((req, res, next)=> {
  res.status(404);
});

app.listen(app.get("port"), ()=> {

});


Then after that, we use the render() method to display the 404 page.

app.js
app.get("/thankyou", (req, res)=> {
  res.render("thankyou");
});

app.use((req, res, next)=> {
  res.status(404);
  res.render("404");
});

app.listen(app.get("port"), ()=> {

});


We will, of course, want to create that page under the views directory.

view/404.handlebars
<h1>404</h1>

<p>Not found!</p>


Now try this. Enter any string after the base URL, other than "thankyou". You should get this.


Now for the rest of the form!

We've learned quite a few concepts. But it's time to create the form so that we can use it in the next part of this tutorial. In this file, add the HTML form. It will submit to the process route (which we'll create in the next part of this web tutorial) and the method will be POST.

views/form.handlebars
<h1>Welcome to Teochew Thunder's Xmas Mailer.</h1>
<p>Email a friend!</p>

<form action="/process" method="POST">

</form>


We have here a label tag. Within it is a span tag styled using the CSS class labelText. The label text is "LAYOUT".
views/form.handlebars
<form action="/process" method="POST">
  <label>
    <span class="labelText">LAYOUT</span>
  </label>

</form>


We've got a few more labels. There are break tags in between.

views/form.handlebars
<form action="/process" method="POST">
  <label>
    <span class="labelText">LAYOUT</span>
  </label>

  <br />

  <label>
    <span class="labelText">EMAIL</span>
  </label>

  <br />

  <label>
    <span class="labelText">MESSAGE</span>
  </label>

</form>


In here, we allow the user to select between a red, green or blue layout by means of a drop-down list, id and name ddlLayout. We'll want to set the for attribute in the label for this, too.
views/form.handlebars
<label for="ddlLayout">
  <span class="labelText">LAYOUT</span>
  <select id="ddlLayout" name="ddlLayout">
    <option value="red">RED</option>
    <option value="green">GREEN</option>
    <option value="blue">BLUE</option>
  </select>

</label>


For email, I'm going to use a text input with type attribute set to "email". This will be a required field. The drop-down list we made earlier is also mandatory, but there's a default value in it, so no need to do anything more.

views/form.handlebars
<label for="txtEmail">
  <span class="labelText">EMAIL</span>
  <input id="txtEmail" name="txtEmail" type="email" placeholder="john12345@gmail.com" required />
</label>


For the message, I use a textarea tag. It is also mandatory.

views/form.handlebars
<label for="txtMessage">
  <span class="labelText">MESSAGE</span>
  <textarea id="txtMessage" name="txtMessage" required></textarea>
</label>


Let's go ahead and add a Submit button.

views/form.handlebars
<form action="/process" method="POST">
  <label for="ddlLayout">
    <span class="labelText">LAYOUT</span>
    <select id="ddlLayout" name="ddlLayout">
      <option value="red">RED</option>
      <option value="green">GREEN</option>
      <option value="blue">BLUE</option>
    </select>
  </label>

  <br />

  <label for="txtEmail">
    <span class="labelText">EMAIL</span>
    <input id="txtEmail" name="txtEmail" type="email" placeholder="john12345@gmail.com" required />
  </label>

  <br />

  <label for="txtMessage">
    <span class="labelText">MESSAGE</span>
    <textarea id="txtMessage" name="txtMessage" required></textarea>
  </label>

  <br />

  <button>Go</button>
</form>


Looks... meh. This is going to need more styling.


In this file, add the styling in the style tag. We want all forms (or actually, the one and only form) to have a height, width, and be in the middle of the page, and have a background color.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

</style>


The label tags are set to display as block-level elements, with a bit of a margin at the top, and font is boldened. The span tags that are styled using labelText. They're also rendered as block-level elements so that we can float them left, give them a margin to the right, and a width.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

  label {
    display: inline-block;
    margin-top: 0.5em;
    font-weight: bold;
  }

  .labelText {
    display: inline-block;
    width: 5em;
    margin-right: 1em;
    float: left;
  }

</style>


Now, we'll make sure all the textboxes and drop-down lists have fixed widths and heights, round corners and a border.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

  label {
    display: inline-block;
    margin-top: 0.5em;
    font-weight: bold;
  }

  .labelText {
    display: inline-block;
    width: 5em;
    margin-right: 1em;
    float: left;
  }

  input, select, textarea {
    border-radius: 5px;
    width: 15em;
    height: 1.5em;
    border: 2px solid rgb(100, 100, 100);
  }

</style>


But textarea tags will have a bigger height.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

  label {
    display: inline-block;
    margin-top: 0.5em;
    font-weight: bold;
  }

  .labelText {
    display: inline-block;
    width: 5em;
    margin-right: 1em;
    float: left;
  }

  input, select, textarea {
    border-radius: 5px;
    width: 15em;
    height: 1.5em;
    border: 2px solid rgb(100, 100, 100);
  }

  textarea {
    height: 5em;
  }

</style>


And lastly, we style the button.

views/layouts/main.handlebars
<style>
  body {
    font-family: verdana, sans-serif;
    font-size: 14px;
    background-color: rgba(255, 200, 0, 0.5);
    color: rgba(255, 100, 0, 0.8);
  }

  h1, p {
    text-align: center;
  }

  form {
    width: 22em;
    height: 12em;
    padding: 1em;
    display: block;
    margin: 0 auto 0 auto;
    background-color: rgba(255, 200, 0, 0.5);
  }

  label {
    display: inline-block;
    margin-top: 0.5em;
    font-weight: bold;
  }

  .labelText {
    display: inline-block;
    width: 5em;
    margin-right: 1em;
    float: left;
  }

  input, select, textarea {
    border-radius: 5px;
    width: 15em;
    height: 1.5em;
    border: 2px solid rgb(100, 100, 100);
  }

  textarea {
    height: 5em;
  }

  button {
    display: inline-block;
    width: 3em;
    float: right;
  }

</style>


And now it looks acceptable.


Next

Form handling and security

Friday, 13 December 2024

Film Review: Black Mirror: Bandersnatch

It's Black Mirror movie time!

Today, I'm reviewing Bandersnatch, which came out in 2018. The format of the film is interesting, to say the least.


Not all of the content is linear, which may be confusing if you're going to watch it, as-is, from beginning to end. From time to time, the show will rewind and the user will be presented with a choice.

Warning

Spoilers! There's a lot of surprises in store within Bandersnatch and they're going to be exposed here as I review this. Some of them may even be upsetting due to sensitive subjects such as suicide and mental health.

The Premise

It's 1984. Stefan is an aspiring game designer that gets to work with the game company Tuckersoft. However, as he progresses though his project, a game based on the novel Bandersnatch by Jerome F Davies, strange things begin to happen.




The Mood

It starts out light-hearted, with the imagery of the 80s everywhere. Until strange things begin to happen the further we go down the rabbit hole of Stefan's obsession.



As it goes on, even innocuous things like Stefan's programming notes leads to reveals of what looks like a paper infestation. It's both ridiculous and creepy at the same time.

The Characters

Fionn Whitehead is the protagonist Stefan Butler. Awkward, neurotic, anxiety. Tense relationship with his father due to unresolved childhood trauma.

Craig Parkinson takes on the role of Stefan's dad, Peter Butler, a caring but stressed out father. In some branches of the story, he has to play the role of sinister agent, but it seems to fall a little flat.

Will Poulter fills the screen with his portrayal of games programmer superstar Colin Ritman. Poulter is a personal favorite of mine - I've enjoyed him in Chronicles of Narnia: Voyage of the Dawn Treader, Maze Runner and Guardians of the Galaxy. That eyebrow game is strong, and that cool older brother vibe he brings to the role is very much on point.

Asim Chaudhry as Mohan Thakur, the jolly but excitable company boss. Just did enough to not be over-the-top, and I genuinely felt bad when the character died in one of the story branches.

Alice Lowe as Stefan's therapist, Dr R Haynes. Don't think we ever find out what the R stands for, but perhaps that's not terribly important. She's there for some exposition as to what's going on with Stefan, and ham it up in one story branch.

Fleur Keith as Stefan's mother. Doesn't get a lot of lines, but she looks exotic, like a gypsy. I'm not sure if that was a deliberate design decision to show how she looks as part of Stefan's memory.

Tallulah Haddon as Kitty, Colin's partner. Colin casually speaks with her about being "in the hole", and I got the distinct impression that she knows stuff. Before that, the impression was all in her makeup and that shocking orange hair.

Suzanne Burden and Paul M Bradley as game reviewers. The look seems reminiscent of the 60s, which is weird because this is supposed to be the 80s.

Alan Asaad as Satpal. He's ostensibly a programmer, but seems to get treated like some kind of gofer in the opffice. The role looked superfluous to me.

Jeff Minter has a cameo as as the author of Bandersnatch, Jerome F. Davies. Only really makes one live action appearance in a jump scare, but it's so good. And, get this, this guy's not an actor, he's an actual games programmer!

Laura Evelyn makes an appearance as Pearl Ritman, Colin's daughter years later.

What I liked

There's a sequence of banter between Colin, Thakur and Stefan in Stefan's first "try", and in the second "try" it goes almost the same, with the same words being said but by different participants in the conversation. It's pretty cool!


The idea of being able to select how the story goes, is quite novel. Can't see it catching on, though.



This symbol made a comeback! I first saw it in White Bear. Too bad there doesn't appear to be a very direct connection.


I just about choked with laughter at this story branch. So cheeky.


Just the whole aesthetic of the 80s, where everyone was smoking everywhere. The clunky computer game graphics of that era. So much charm.


And of course, the numerous references to earlier episodes in Black Mirror. This one in particular to Metalhead.

What I didn't

There's one story branch where it's revealed that Stefan is actually an actor named Mike. That's pretty meta. Know what would have made it even more meta? If the actor's name had been Fionn.

Some of the story branches are gratuitous and a bit superfluous. I mean, do we really need a branch where Stefan chops up the body of his dead dad?

Conclusion

It's decent, not great. But it can only be a one-off thing, because I can see this concept getting tiresome if repeated.

My Rating

6.5 / 10

Reread this review
Pour coffee on your keyboard
Jump out the window
T___T