Skip to content

How to Write a New Feature

7sempra edited this page Jan 10, 2019 · 3 revisions

Adding a feature usually involves some or all of the following:

  • Create/modify database tables. We use Postgres. It sits behind a light abstraction layer that makes most database queries typesafe-ish.
  • Create a task. Tasks are chunks of code that do a thing and then finish. These can be run manually from the admin dashboard or scheduled to run periodically. They usually sync data from ESI or another source, such as roster membership stats or recent killmails.
  • Add new HTTP endpoint(s). These are served by the webserver and typically return JSON. For example, https://<hostname>/api/character/<characterId>/skills will return a JSON blob of a character's skills.
  • Add new UI. The roster is a single page web app (SPWA), powered by the Vue framework.

Create/modify database tables

Modify table declarations

First, modify src/db/tables.ts to reflect the changes you want to make. This won't actually affect the DB; it just tells Typescript what the shape of the tables are.

Each class represents a table and each member of that class represents a column. The name of each member should begin with the same table prefix. For example, the CharacterLocation table prefixes all of its members with charloc_. The prefix can be anything as long as it is unique to that table.

Note that the prefix should not appear in your migration file (see below). Prefixes don't exist in the DB proper, only in Typescript land. See the Tnex page for more info.

Add/update DAO

Tasks and routes don't access the DB tables directly, they do so via the DAO, which wraps gnarly DB queries with descriptive function names. DAO classes are found in /src/db/dao. You can modify existing DAO categories or add a new one. If you add a new one, inject it into /src/db/dao.ts.

Create migration file

Finally, create a file in /schema/XXX-my-feature.js.

  • It must export two functions named up() and down()
  • up() must apply the changes you want to make. down() must revert those changes.
  • Each function will be passed a trx object, which is an instance of Knex. See their schema builder documentation on the kinds of commands available. Also check out existing files in that directory for examples of what can be done.

Once your migration file is created, make sure you've built the codebase:

$ npm run build-server

Then run the migration:

$ nf run npm run updatedb

Check to make sure your rollback method is written correctly

$ nf run npm run updatedb -- -- --revert

...then run updatedb once more to keep your changes.

Create a task

  1. Define your task in /src/tasks. Your task object must implement the Task interface.
  2. If you want your task to be runnable from the admin UI (you probably do), add it to src/task-registry/runnableTasks.ts.
  3. If you want your task to run periodically, add it to src/task-registry/scheduledTasks.ts.

Create an HTTP endpoint

Create a new entry at the appropriate place in /routes/....

If your endpoint returns JSON (this is probably true), it should be under /routes/api/.... Use the jsonEndpoint function to declare the endpoint (see other routes for examples). Once you've written the endpoint, register it in /routes/api.ts.

Add new UI

Location: /src/client/...

The frontend is built on the Vue framework. Their documentation is excellent and worth the read.

A Vue UI is a tree of components. The root component takes in a big blob of data, breaks it up into chunks, renders some of it, and hands the rest off to some sub-components.

Each component is defined in a .vue file. A .vue file has three parts: HTML (template), JavaScript, and CSS. The HTML templates contain special directives for if-statements, for-loops, etc. The template syntax documentation is worth a skim. The structure of the JavaScript also relies heavily on the Vue API. The CSS is just CSS.

To create a new page, do the following:

  1. Create a new Vue component (*.vue) that will render your UI.
  2. In src/client/home.js register the route that your component will be served from. For example, a hugging-based UI might create routes like /hugs (for the landing page, component=HugsMain) and /hugs/:characterId (for character-specific stats, component=HugsCharacterDetail).

See the vue-router documentation for more information on how routing works.