$ yarn install
Local development and tests are configured to use PostgreSQL in a Docker container.
Docker must be installed and running for any development work.
-
Start the PostgreSQL container with
yarn postgres
. Development roles and databases are automatically created on the initial run. -
Run
cp .env.development .env
to use default development database variables during development. -
Run
yarn pristine
, which resets the database and runs the migrations and seeds. You will need to re-run this command when the database schema is changed. Warning: all existing data in the database will be lost!
-
You can access the PostgreSQL CLI with
yarn psql
. All column names that are in camelCase need double quotation marks when used in raw SQL queries (i.e.SELECT body, "userId" from posts;
). -
If you want to, you can run postgres on your host machine and point to the port (usually 5432) in
.env
. If you do so, you must configure postgres to use the UTC timezone (by editing.conf
and restarting the postgres process)! The docker postgres and most servers use UTC by default so it's not a problem, but your local postgres may be configured in your local timezone. If your postgres is not set to UTC, you will get a nasty bug when editing/creating Meets!
A shell script db/pgdump_production.sh
can be executed to make a dump of current production data into a backups
direction (gitignored).
This operation requires access to the production server.
Note: the script calls ssh MintbeanV3Production
, meaning you must name your host connection MintbeanV3Production
in ~/.ssh/config
on your machine
Host MintbeanV3Production
HostName <HOST NAME OMITTED>
User ubuntu
IdentityFile ~/.ssh/MintbeanProduction
- Setting up the local database
- Run
yarn dev
to start a GraphQL endpoint atlocalhost:4000/graphql
. - Go to
localhost:4000/graphql
to use the GraphQL Playground.
Running yarn gen-types
auto-generates a TypeScript definition file using graphql-codegen
. The tool reads the schema file in ./src/graphql/schema.ts
and outputs TypeScript definitions in ./src/graphql/generated/tsTypes.ts
. You may then import types directly from that file, such as:
from "./generated/tsTypes";
Resolvers are also automatically typed, i.e:
import { Resolvers } from "../generated/tsTypes";
const postResolver: Resolvers = {
Query: {
// Rest of code
},
};
Remember to run yarn gen-types
after every schema change, to ensure tsTypes
is up to date and you don't get TS errors. This is unnecessary if you're in hot-reload/watch mode with yarn dev
, as the generator tool automatically watches for schema changes.
- Create the new typeDef file for the entity in
./src/graphql/typedefs/entity.ts
. - Run
yarn gen-types
to update theResolvers
type in Typescript type so you can take advantage of typing when making the resolver. - Create a new resolver file for the entity in
./src/graphql/resolvers/entityResolver.ts
. - Add the typedef and resolver into the corresponding array of the schema generator file in
./src/graphql/schema.ts
.
-
Start the PostgreSQL container with
yarn postgres
. Test roles and databases are automatically created on the initial run. -
Run
yarn pristine
to set up the test database migrations. -
Run
yarn test
for a single test with coverage report, oryarn tdd
to run tests in watch mode as part of a TDD workflow.
Script | Description |
---|---|
start |
Starts the server with ts-node |
dev |
Starts the server with ts-node & hot-reload |
build |
Builds *.ts in ./src to *.js in ./build |
db:reset |
Drops and recreates all databases, nuking all the tables |
gen-types |
See above |
knex |
Runs knex cli tools for migration/seeds, using the default database specified in .env |
knex:test |
Runs knex cli tools for migration/seeds, using the test database (specified in ./test/.env.test ) |
postgres |
Starts the Postgres docker container |
pristine |
Runs db:reset then runs all the migrations and seeds on both the dev and test databases. |
psql |
Enters the psql CLI in the docker container |
tdd |
Runs the tests in watch mode for a TDD workflow |
test |
Runs the tests once and generates a coverage report |
dump:prod |
Dumps production backups into db/backups/* |
rollback:db |
Clears knex's cache of past migrations. This is useful when switching between branches with different migrations in development if you encounter corrupt migration error. After rolling back, use yarn pristine to build tables with current branch's schema |
Prepend commands below with either yarn knex
to target the default db (specified in .env
) or yarn knex:test
to target the test db (specified in ./test/.env.test
).
Command | Description |
---|---|
migrate:latest |
Runs all migration files in ./src/db/migrate |
migrate:rollback --all |
Undos all previous migrations |
seed:run |
Runs all the seed files in ./src/db/seeds in alphabetical order (drops all previous data first) |
migrate:make <name> |
Makes an empty timestamped migration file in ./src/db/migrate |
seed:make <name> |
Makes an empty seed file in ./src/db/seed |
- ssh into prod
- in home dir, backup the db:
sudo -u postgres pg_dumpall --column-inserts > backups/backup.production.\$current_time.sql
- cd into backend dir
- stop backend service
pm2 stop "backendv3"
- git checkout the appropriate branch
- git pull
yarn install
yarn knex migrate:latest
pm2 start yarn --name "backendv3" -- start
- Setting up the local database
- Run
yarn start
. - Navigate to
localhost:4000/graphql
on your browser to use the GraphQL playground (NODE_ENV
must not be set toproduction
). - Click
DOCS
tab on the right side to explore the API reference. - Click through the definitions to explore them in more detail:
Jobs are standalone scripts that can be run via yarn jobs:<job-name>
.
These scripts can be found in src/jobs/
. Jobs use a separate JobContext
to provide access to any constructed entities likes Daos/Services, such that they can run independently of the application itself.
We use the server's pm2
built in cron job scheduler to start jobs that need to be automatically run on a regular schedule.
After learning cron syntax you can test your cron expressions here.
example:
pm2 start cron.myCronJob.js --cron "*/1 * * * *"
^ "Run cron.myCronJob.js
every minute"
Since cron syntax only allows minimum scheduling granularity of 1 minute and we want to run the email job every 5 seconds, we run a script cron.email.js
that itself allows running a script every n seconds using setTimeout()
. The interval is currently set to every 5 seconds
pm2 start cron.email.js --cron "*/1 * * * *"
^ runs cron.email.js
every minute, which itself runs yarn jobs:email
every 5 seconds
Retrieves all scheduledEmails
due for sending, builds emails, sends, and logs and handles responses.
It is suggested this job be scheduled to run every 5 - 10 seconds, as to provide near-immediate sending of emails queued to be sent "now", like registration confirmations.
Emails that fail sending for whatever reason are left in the queue, with a maximum of 3 retries.
For scheduledEmails
with mulitple recipients (i.e the meetRecipientId
field is present), a member email that fails sending is requeued as a single scheduledEmail
record with a specified single userRecipientId
and given two retries.
The kanban is a workflow board with status columns and card items that can be moved between those columns (x dimension) and change order within a column (y dimension). We currently have three available status columns: TODO
, WIP
and DONE
.
There are two important entities to be aware of: KanbanCanon
and Kanban
.
KanbanCanon is the "master" kanban maintained by an admin, with a title, decscription and master KanbanCanonCards
Kanban is the individualized view of a kanban for a given user. It contains kanbanCards
that persist their position after the user has moved them.
kanbanCards
on a user kanban
are really just kanbanCanonCards
resolved under a different name.
The positional data for kanbanCanonCards and kanbanCards are stored as a serialized JSON object in a column called cardPositions
on the kanbanCanonCards
and kanbanSessionCards
tables in the database.
Some utilities in cardPostionUtils.ts
help to resolve and update cardPositions
when moving, adding and deleting kanban cards.
In this manner, no positional data is sent on the kanbanCanonCards or kanbanCards themselves, rather as a cardPositions
object on kanbanCanon
and kanban
that reference kanbanCanonCard Ids. The client (ex: frontend) can then map the cards to the positoins in cardPositions
.
All application emails are sent via a queue system stored in the scheduledEmails
table in the database. The queue should be periodically checked (by cron job) for emails that need to be sent, and those emails built and dispatched.
The scheduledEmails
table provides instructions for how to build an email and resolve it's recipients. Here are the important fields(* = not nullable):
id | templateName | userRecipientId | meetRecipientId | meetId | sendAt | retriesLeft |
---|---|---|---|---|---|---|
*UUID | *text | UUID | UUID | UUID | *timestamp | integer |
templateName
is a string pointing to the filepath of the .ejs
templates for this email. templateName
entry is restricted by Typescript enum EmailTemplateName
userRecipientId
is specified if the email is for a single user (registration confirmations).
meetRecipientId
is specified if the email is for all registerants of a meet (meet reminders)
meetId
is the meet that is the topic of the email
sendAt
is the threshold of when to trigger sending this scheduled email job.
retriesLeft
number of attempts at sending remaining.
When the yarn jobs:email
job is fired, all scheduledEmails
ready for sending (i.e. sendAt
< now ) are selected via EmailService#getEmailsToBeSent()
which also builds EmailContext
by inflating the values in the table, resolving recipients, and building ics calendar attachments if necessary.
Once the EmailContext
is built, the recipients are looped over to build user-specific templated emails. These templated emails are then sent one by one via EmailApiDao#send(email)
.
scheduledEmails
that suceed in sending to one or more recipients are deleted.
Any email that fails sending is either left (in the queue in the case of single recipient scheduledEmails
), or re-queued as single recipient scheduledEmails
(in the case of bulk recipient scheduledEmails
) with 2 retries remaining. They will be attempted again the next time the yarn jobs:email
is run.
Logging occurs for all email responses (including success). Keep an eye on the server logs to see whether any maintenence is required.
Note: emails that fail 3 times are left in the database and will pool if not maintained. Run checks and clean periodically.
We currently have 2 email flows:
- When user registers for meet, send immediate registration confirmation with attached calendar invite
- When a new meet is created, queue two reminder emails leading up to meet startTime (1: start-24hrs, 2: start-30mins). If meet is a hackathon, queue two additional emails for submission deadline reminders (submission remider 1: (only sent if meet > 3 days long) endTime - 2 days; submission reminder 2: (if meet > 1 day long): endTime - 3 hours, otherwise endTime - 30 mins) *Warning! meet reminder timing is currently not updated if meet startTime or endTime changes. TODO.
Calendar invites for hackathons are scheduled as a 1 hour Kickoff from startTime
instead of blocking 7 whole days of a user's calendar.