read24 - Server: Admin Routes

July 11, 2020

Roger Ngo

I have pretty much the necessary endpoints written in a manner which satisfies the scenario when a student may take a quiz from beginning to end. The problem is that up until now, most testing to validate my code changes have been through the same small set of seed data found in mockDb.json since it was created.

I am personally itching to have more interesting data. While I can write a script to seed some students, users, and quiz questions, it is not a very good long term solution.

Instead, I can write a few API endpoints that will handle adding students, and quizzes into the application. This will bring a couple of benefits:‌

  1. I can script insertion of data through calling the API using HTTP requests.
  2. I can also create a front-end management system which can add data in a user-friendly way.‌

The first use case is very cool and caters to my natural software developer mindset where it is preferable to automate as much as possible. However, the second case in creating the API endpoints for a nice front-end is definitely the more interesting problem to tackle, and solution to show off to my family and friends.

With that admittedly admitted, let's build out the administration API, followed by an Administration web-suite for read24.

Use Cases

Before I start slamming on the keyboard to implement the administration API, let's think about some use cases:

  1. The user who can administrate the application is a Teacher.

  2. The teacher can manage a set of Student belonging to a Classroom.

    1. View students (List through a GET)
    2. Add a single student (POST)
    3. Remove a single student (DELETE)
    4. Edit a single student (PUT)
  3. The teacher (for now) can load quizzes. For now, I envision it to be a manual data entry process. I am not concerned about the UI right now, but I do imagine a separate page is needed to build out quiz data composed of a book, its possible questions, and possible choices.

    1. The page should display data entry fields for:

      1. Book information
      2. Possible questions, and for each question, the set of (4) possible choices with a single correct answer specified.
    2. Submitting this form should cause data to be serialized into a custom JSON data format which will then be received by the server to save into the database.

Building on top of point 3 above, if one is able to create quiz data, one should also be able to edit the quiz data from the front end. Therefore, it is mandatory to provide an API endpoint to retrieve quiz content, and convert the data found into JSON data format for the page to render. Once saved, the opposite must happen and the data must be deserialized so that it can be saved to the multiple tables in the database.

This means that utility methods of saveBookAsQuiz(), and readBookAsQuiz() should be the methods containing the logic to serialize the book, quiz question and choices to JSON, and deserialize the JSON back to the individual objects respectively. The front-end should be able to read in this format, and populate the page with the quiz data.‌

Yes, we're building a big CRUD app!

API Specification

Alright, now that I have a narrow scope on the use cases that I care about, I'm going to communicate exactly what API endpoints I think should be useful immediately. I have divided them into 2 groups: Student Management, and Quiz Management.

Student Management

These endpoints relate managing students. Fairly straightforward.‌

Quiz Management

The operation to add a book to the library means to not just add Book data, but to also add a quiz (set of questions + choices) for that book. Therefore, from the client's perspective, Books will always have a set of questions and choices attached to them for the platform to construct a quiz. ‌

In other words, given any book, there should always be a quiz which can be generated.

This is not set in stone yet, but I imagine the JSON to create, and edit the book + quiz information to be something like this:

{
    "Book": {
        "title": ...
    },
    "Quiz": [
        {
            "Questions": {...},
            "Choices": [{...}, {...}]
        },
        {
            "Questions": {...},
            "Choices": [{...}, {...}]
        },
        ...
    ]
}

Stubbing

Now, let's make the admin routes. Create a new file called admin.ts under routes folder. I will stub all endpoints for now.

import { IRouter } from "express";

export function mountAdmin(app: IRouter) {
    /**
     * Admin Student Routes
     */

    app.get('/admin/classroom/:classroomId/students', (req, res) => {
        const classroomId = req.params.classroomId;

        return res.status(200).json({classroomId});
    });

    app.post('/admin/classroom/:classroomId/students', (req, res) => {
        const classroomId = req.params.classroomId;

        return res.status(200).json({classroomId});
    });

    app.put('/admin/classroom/:classroomId/students', (req, res) => {
        const classroomId = req.params.classroomId;

        return res.status(200).json({classroomId});
    });

    app.get('/admin/classroom/:classroomId/students/:studentId', (req, res) => {
        const classroomId = req.params.classroomId;
        const studentId = req.params.studentId;

        return res.status(200).json({classroomId, studentId})
    })

    app.delete('/admin/classroom/:classroomId/students/:studentId', (req, res) => {
        const classroomId = req.params.classroomId;
        const studentId = req.params.studentId;

        return res.status(200).json({classroomId, studentId});
    });

    /**
     * Admin Quiz Routes
     */

     app.post('/admin/quiz/import', (req, res) => {
         return res.status(200).json({});
     });

     app.put('/admin/quiz/import', (req, res) => {
         return res.status(200).json({});
     });

     app.get('/admin/quiz/book/:bookId', (req, res) => {
         const bookId = req.params.bookId;

         return res.status(200).json({bookId});
     });

     app.delete('/admin/quiz/book/:bookId', (req, res) => {
         const bookId = req.params.bookId;

         return res.status(200).json({bookId});
     });
}

I think that's enough for today!