-
Notifications
You must be signed in to change notification settings - Fork 3
How to Write a New Feature
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.
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.
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
.
Finally, create a file in /schema/XXX-my-feature.js
.
- It must export two functions named
up()
anddown()
-
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.
- Define your task in
/src/tasks
. Your task object must implement the Task interface. - If you want your task to be runnable from the admin UI (you probably do), add it to
src/task-registry/runnableTasks.ts
. - If you want your task to run periodically, add it to
src/task-registry/scheduledTasks.ts
.
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
.
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:
- Create a new Vue component (*.vue) that will render your UI.
- 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.