Every year, from 1974, the School of Computing and Mathematics at Georgia Southwestern State University (GSW) has hosted a tournament for high school students from throughout the State of Georgia. This tournament has brought hundreds of students to the campus in the early spring each year to compete with one another. The need to host hundreds of students at the same time while giving them opportunity to individually work on the same set of problems, and mainly important, to process their answers in quickest possible manner enforced us to run the tournament with the help of Scantron technology.
Before I joined the project the following workflow was used:
I joined the project in 2015 and reworked the registration form to support front-end validation. It was the time when I just started learning web technology so I implemented the task using jQuery UI. I ended up with constructing a landing page with modal dialog that was collecting form data and pushing it to PHP backend for processing. Our processing was as simple as it can be: (i) calculate the total cost; (ii) send out two customized e-mails: one to team sponsor and one to organizers.
I was told that the old Windows 98 PC that we use to control Scantron machine is about to die. I proposed replacing it by a RasPi board that would mimic proprietary Scantron protocol. Since Scantron and PC were connected by means of RS-232 interface I decided to try to sniff on their exchange. We’ve purchased a SerialGhost Pro dongle and grabbed a few sessions where PC was controlling Scantron to handle a number of typical scanning scenarios including
The implementation worked as expected and my next move was to build a web interface running off of RasPi, so we could control the Scantron in a bit more user friendlier manner. Since Node.js was already under the hood of the project, I added Express to serve static HTML5 bootstrap flavored frontend and used Socket.io to support asynchronous communication between the frontend (web UI) and the backend (Scantron controller class). After all programming improvements I packaged RasPi board into a touchscreen enabled case and that was a complete product… I thought it was.
The last think to do was to replace the C++ program that was previously used to process scan data by a web facing solution, so I ended up with jQuery + Bootstrap HTML5 document, capable of merging and processing raw registration data along with scan data to generate persistent reports.
Both RasPi scanner and report generator were tested during 2017 Math Tournament event and worked pretty well.
After working with different parts of the Math Tournament registration portal it was a pretty obvious move to integrate them under umbrella of a single web application capable of addressing the following tasks
I started the HMT project in the beginning of Fall 2017, and at that time, I already had some experience with full stack development using Node.js + MongoDB on the backend, and jQuery + Bootstrap on the frontend. I decided to make one step further and started learning Vue.js frontend framework in parallel. The following list shows technological skeleton of the HMT Project:
The GitHub project page is available at https://github.com/gswcm/hmt.
Deployment of the application should not face any bumps, but there are a number of issues that have to be carefully addressed:
/configs/nginx. The configuration assumes that repo either is cloned into
/var/www/appor a symbolic link
/var/www/apppoints to a real location of repo files.
reCAPTCHA_SECRETenvironment variables set with values configured on http://www.google.com/recaptcha/admin page.
S3_SECRETenvironment variables have to be set to store corresponding values for S3 bucket named
hmt.gswcm.net. Bucket name can be modified in
npm run buildfrom the repo directory.
node ./bin/www.jsor simply
nodemonfrom the repo directory.
NODE_ENV=development nodemonfrom the repo directory.
pm2NPM package and using it to create a form of startup script.
A normal user (team sponsor) visits the landing page of the portal, i.e. https://hmt.gswcm.net and, if the tournament date is set at least 1 days ahead, can proceed with creating a new or revising the previously made registration. Team sponsor is identified by e-mail address that has to be entered into the corresponding input field. Entering of an unrecognized e-mail address leads to displaying of an empty registration form. But if the entered e-mail address is found matching existing registration, the form would be populated with previously entered registration data.
Form validation routines require clearing up all errors in order for the submission button to become enabled. The Invisible reCAPTCHA guards the form from spammers and other automated fillings/submissions.
Once the form is completed and submitted, the registration is treated as unconfirmed or pending until after the sponsor would follow confirmation link from the automated e-mail. Should this happen within the grace period, the same registration info will become available for the last moment editing and final submission.
Important note: The registration process allows for unrestricted submission of registration info. All unconfirmed (pending) registrations expire in 1 day. The registration process is password-free.
The admin role is hardcoded to map selected e-mail addresses and is defined in
./backend/lib/admins.js file. The admin role is associated with tournament organizer(s).
The admin interface will be exposed to users listed as admins (see above) and accessing the registration portal at
/start URL. Such users will be prompted to follow
/admin link to access the actual admin interface. Alternatively, they can play the role of a normal user and continue with normal user workflow as discussed above.
Access to the admin interface is restricted to authenticated users only. The admin membership defined in the
./backend/lib/admins.js file sets a necessary but not a sufficient condition. The admin page available at
/admin exposes two variants of the content matching authorized, and non-authorized sessions. The non-authorized variant is present by default until after valid admin credentials, i.e. e-mail and password are entered. There is no Submit button that can be clicked to initiate credential validation, instead, a mechanism relying on
lodash.debounce() with a delay of 500 ms is used to counteract possible bruteforce attack.
Having specified valid admin credentials a user exposed to the authorized version of the admin interface that is discussed in details in the next section. The prompt for entering email/password is no longer shown, and is replaced by the real content of the page.
This application does not rely on HTTP sessions for authentication purposes. It is done on purpose, to explore the paradigm of the stateless backend — a better fit for scalable implementations. Every request sent from the admin interface to the admin API includes previously entered credentials that are validated prior performing requested admin task. This approach mimics JWT way of sending authentication data along with every request, but does not facilitate digital signatures for data integrity validation.
It is worth stating that since credential data are passed to the backend with no encryption, the app has to be served using HTTPs protocol configured at the Nginx level.
The admin interface features 5 tabs, each responsible for a particular set of tasks:
Records tab allows to work (add/delete/edit) with team registrations. Organizers can navigate through the collection of submitted registrations by means of setting up flexible filters. Any combination of the following criteria can be utilized to display a subset of all submitted registrations:
An exampled view of the Records tab is illustrated on this screenshot: http://nimb.ws/hR9p06.
On top of ability to display a subset of all registration records, this interface allows to work with individually selected team. More specifically, it is possible to
The download action can be applied to the entire list of currently filtered teams, should admin needs to prepare a PDF, say for the entire division.
The introduction of an ability to print student identification data directly on the Scantron sheets has significantly simplified the tournament preparation phase. In the past, organizers would have to print named labels and stick them onto each Scantron sheet. Moreover, specific IDs had to be imprinted (4 marks made by #2 pencil) by hands while leaving great opportunity for a typo or other kinds of mistakes. With the introduction of direct printing on Scantron sheets these problems no longer affect the preparation phase.
Questions tab allows to edit tournament questions, which is to specify category and answer key for every given question. There are 40 questions ranging 5 different categories and allowing 5 possible answers with only one answer being considered as correct.
An exampled view of the Questions tab is illustrated on this screenshot: http://nimb.ws/3j1nQJ
Scantron tab allows to interact with Scantron controller discussed in the dedicated section of this document below. Also it allows to edit a set of scan records currently stored in the database. Taking into account the possibility of submitting duplicated scan records, the interface introduces 2 ways of uploading scan data to the database:
An exampled view of the Scantron tab is illustrated on this screenshot: http://nimb.ws/L1SoPF
Results tab mimics presentation of the Results page available at
/stats while adding a number of extra features
An exampled view of the Results tab is illustrated on this screenshot: http://nimb.ws/6ivMpb
Maintenance tab alows to perform various maintenance tasks like removing stale (unused) accounts, re-shaping collection of team registrations, rolling over the application for the next year tournament, and setting up date of the tournament event that affects appearance of many portal components.
An exampled view of the Maintenance tab is illustrated on this screenshot: http://nimb.ws/MnxCa1
The portal is designed to respect various devices ranging from a tiny screen of iPhone 5 to extra large screen of a modern desktop computer. All heavy lifting is implemented by utilizing various layout classes of Bootstrap 4. Here are a few examples of how admin UI is modeled to look on iPhone 7:
And last but not least, this project is known to have a number of issues summarized below:
vuexstore. This information is considered to be protected by
vuexmechanisms as long the session is active, but in some situations this information can (I presume) be hijacked from the store upon session completion.
The HMT Project wouldn’t be complete without ability to automate scanning process. While working on HMT portal I hoped to re-use the previously developed Scantron controller (see above) after minor adjustment… but that was not going to happen. I couldn’t accept my own code styling, classes organization, pretty much everything except the core implementation of the controller logic. Moreover, the
serialport project has evolved from v4 to v6 and many things that my implementation relied upon became non-functional.
I decided to rework the entire piTron project to have
The renewed piTron also featured Scantron Simulator — yet another SerialPort based JS class mimicing lifecycles of a real Scantron. Having both piTron and Scantron Simulator connected on the hardware level (GND-GND, Tx-Rx, Rx-Tx) I managed to debug piTron logic and its UI without running actual scans.
The piTron project has its own GitHub repo available at https://github.com/gswcm/pitron.
The following video demonstrates piTron + HMT bundle in action.