Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wire up a database #8

Merged
merged 7 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CI=0
DATABASE_URL="postgresql://daedalus-web-app-user:changeme@localhost:5432/daedalus-web-app"
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ jobs:
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: j-idea/daedalus-web-app
slug: jameel-institute/daedalus-web-app
fail_ci_if_error: true
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,31 @@ npm run test:e2e

The tests under e2e, which are run by playwright, are for testing the full-stack (client app and server app) in a browser environment. Since the server-rendered page may be different from the client-rendered page, for example, when some elements are configured to only render on the client side, relevant tests should wait for the elements to be present.

## Setup
## Local development

### Setup

Make sure to install the dependencies:
Use Node 20.
Have Docker installed.
Copy `.env.example` to `.env`.
Build and run the database container:

```bash
./db/scripts/build
./db/scripts/run
```
Comment on lines +36 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're going to have to do this a lot during development, I think it would be better to make this a single script call - you might as well put that in a run-dependencies now since you know you're going to have to include the api soon too. It should apply the db migrations you need to.

Do we need to distinguish .env from .env.example here? Can't we just make .env the settings that will work for local development and then in the deployment tool we'll make sure we overwrite with whatever settings we need for the environments we're deploying?

Copy link
Contributor Author

@david-mears-2 david-mears-2 Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To streamline setting up .env, in the next PR I'm updating the README to make copying and npm installing a single action:

Copy .env.example to .env and install the JS dependencies:

cp .env.example .env
npm install


Install the JS dependencies:

```bash
npm install
```

## Local development
Prisma ORM can only query the database once you 'generate' the Prisma Client, which generates into `node_modules/.prisma/client`. This should happen when you install the JS dependencies and whenever you run a migration, but if the Prisma client gets out of sync or doesn't generate, you can manually generate it:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..and this generates the client based on schema.prisma?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the README to make this clear


```bash
npx prisma generate
```

Start the development server on `http://localhost:3000`:

Expand All @@ -51,6 +67,20 @@ npm run dev -- --host

The QR code shown will allow you to quickly access the app.

### DB

To create migrations to the database, first update the Prisma schema at ./prisma/schema.prisma as required, then run the below to generate the corresponding SQL migration and to apply it to the database:

```bash
npx prisma migrate dev
```

The same command is also used to apply migrations that already exist in ./prisma/migrations but which have not been applied to the database.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so we're going to keep this as a manual development process? When you need to update the schema, you'll update both the .schema file, and generate the SQL migrations, and commit both to git?

..and as well as generating the migrations this command also applies them to the running database (defined in .env) - running in the container. So we'll run ths same thing as part of deployment (?) to update the db?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Updating README to add "You should commit both the Prisma schema and the migration file to Git."
  • Will look into the precise process for how migrations are done in deployment, but probably it's a GitHub Action that runs the command prisma migrate deploy (as opposed to the dev-specific command above).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we just need to run prisma migrate deploy in a github action, and remember to set the DATABASE_URL env var (which we can do in github per-environment).

https://www.prisma.io/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate

Copy link
Contributor Author

@david-mears-2 david-mears-2 Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes most sense to try that out when we have some environments up, with databases running, which we can see whether or not they correctly get migrated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self for when I'm working on this:

It might be the case that we need to explicitly re-generate the prisma client (using prisma generate) after running prisma migrate deploy, since that command "does not reset the database or generate artifacts (such as Prisma Client)"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now have a 'Add prisma migrations to deploy workflow' ticket.


#### For your IDE

In VSCode, you can use the extension with ID 'Prisma.prisma' to get syntax highlighting etc.

## Linting and formatting

Linting and formatting are handled jointly by [@nuxt/eslint](https://eslint.nuxt.com/packages/module) (an "all-in-one" ESLint "integration" for Nuxt) and by the more frequently-updated, conventionally- and widely-used [@antfu/eslint-config](https://github.com/antfu/eslint-config) (antfu works at NuxtLabs, and the package is given as an example in the `@nuxt/eslint` docs). The former handles linting only, while the latter also handles formatting, based on ESLint Stylistic.
Expand Down
27 changes: 27 additions & 0 deletions db/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM postgres:14.12
COPY bin /daedalus-web-app-bin
ENV PATH="/daedalus-web-app-bin:$PATH"
ENV POSTGRES_DB=daedalus-web-app
ENV POSTGRES_USER=daedalus-web-app-user
ENV POSTGRES_PASSWORD=changeme
# This is needed to override the loss of data that happens if you
# don't mount a persistent volume at the mount point.
ENV PGDATA=/pgdata

COPY conf /etc/daedalus-web-app

RUN cat /etc/daedalus-web-app/postgresql.conf /etc/daedalus-web-app/postgresql.test.conf.in > \
/etc/daedalus-web-app/postgresql.test.conf
RUN cat /etc/daedalus-web-app/postgresql.conf /etc/daedalus-web-app/postgresql.production.conf.in > \
/etc/daedalus-web-app/postgresql.production.conf
RUN chown -R postgres:postgres /etc/daedalus-web-app

# Ensure docker-entrypoint.sh is in the PATH
ENV PATH="/usr/local/bin:$PATH"
RUN docker-entrypoint.sh --version

# Ensure the start script is executable
RUN chmod +x /daedalus-web-app-bin/start-with-config.sh

ENTRYPOINT ["/daedalus-web-app-bin/start-with-config.sh"]
CMD ["/etc/daedalus-web-app/postgresql.conf"]
17 changes: 17 additions & 0 deletions db/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Build docker image for postgres database:

```
./db/scripts/build
```

Run docker container for postgres database:

```
./db/scripts/run
```

Run an SQL query:

```
./db/scripts/runQuery "SELECT id FROM scenario"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

```
8 changes: 8 additions & 0 deletions db/bin/start-with-config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

# Copied from https://github.com/mrc-ide/packit/blob/main/db/bin/start-with-config.sh

set -ex
CONFIG_FILE=$1
shift
exec docker-entrypoint.sh $* -c config_file=$CONFIG_FILE
6 changes: 6 additions & 0 deletions db/conf/pg_hba.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all all md5
host replication all all md5

# Copied from https://github.com/mrc-ide/packit/blob/main/db/conf/pg_hba.conf
17 changes: 17 additions & 0 deletions db/conf/postgresql.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copied from https://github.com/mrc-ide/packit/blob/main/db/conf/postgresql.conf

# These are the non-commented values from postgresql.default.conf
# *except* for the hba_file one.
listen_addresses = '*'
hba_file = '/etc/daedalus-web-app/pg_hba.conf'
max_connections = 100
shared_buffers = 128MB
dynamic_shared_memory_type = posix
log_timezone = 'UTC'
datestyle = 'iso, mdy'
timezone = 'UTC'
lc_messages = 'en_US.utf8'
lc_monetary = 'en_US.utf8'
lc_numeric = 'en_US.utf8'
lc_time = 'en_US.utf8'
default_text_search_config = 'pg_catalog.english'
107 changes: 107 additions & 0 deletions db/conf/postgresql.production.conf.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# -------------------------------------
# PostgreSQL/hint configuration file
# -------------------------------------

# Copied from https://github.com/mrc-ide/packit/blob/main/db/conf/postgresql.production.conf.in

# This file has been stripped down to the options that we are actually
# setting to make it easier to work with.
# Options are documented online here:
# https://www.postgresql.org/docs/9.6/static/runtime-config.html

#------------------------------------------------------------------------------
# FILE LOCATIONS
#------------------------------------------------------------------------------


#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
#------------------------------------------------------------------------------

listen_addresses = '*'
max_connections = 100

#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
#------------------------------------------------------------------------------

shared_buffers = 8GB
work_mem = 64MB
maintenance_work_mem = 1GB

dynamic_shared_memory_type = posix

#------------------------------------------------------------------------------
# WRITE AHEAD LOG
#------------------------------------------------------------------------------

wal_level = replica
max_wal_size = 1GB
checkpoint_completion_target = 0.9
checkpoint_flush_after = 256kB

#------------------------------------------------------------------------------
# REPLICATION
#------------------------------------------------------------------------------

# This needs to be 3 * max_replication_slots + 1 because each
# replication instance might use three connections
max_wal_senders = 7 # max number of walsender processes
max_replication_slots = 2 # max number of replication slots

#------------------------------------------------------------------------------
# QUERY TUNING
#------------------------------------------------------------------------------

effective_cache_size = 16GB

#------------------------------------------------------------------------------
# ERROR REPORTING AND LOGGING
#------------------------------------------------------------------------------

log_timezone = 'UTC'

#------------------------------------------------------------------------------
# RUNTIME STATISTICS
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# AUTOVACUUM PARAMETERS
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS
#------------------------------------------------------------------------------

datestyle = 'iso, mdy'
timezone = 'UTC'

# These settings are initialized by initdb, but they can be changed.
lc_messages = 'en_US.utf8' # locale for system error message
# strings
lc_monetary = 'en_US.utf8' # locale for monetary formatting
lc_numeric = 'en_US.utf8' # locale for number formatting
lc_time = 'en_US.utf8' # locale for time formatting

# default configuration for text search
default_text_search_config = 'pg_catalog.english'

#------------------------------------------------------------------------------
# LOCK MANAGEMENT
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# VERSION/PLATFORM COMPATIBILITY
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# ERROR HANDLING
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# CONFIG FILE INCLUDES
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
11 changes: 11 additions & 0 deletions db/conf/postgresql.test.conf.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copied from db/conf/postgresql.test.conf.in
# ----------------------------------------------------------------------------
# Test database config begins here.
# Values here override those earlier in the file
# ----------------------------------------------------------------------------
shared_buffers = 128MB
fsync = off
synchronous_commit = off
full_page_writes = off
checkpoint_timeout = 45min
max_wal_size = 4GB
11 changes: 11 additions & 0 deletions db/scripts/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
HERE=$(dirname $0)
. $HERE/common

PACKAGE_ROOT=$(realpath $HERE/..)

docker build \
-t "$TAG_SHA" \
-t "$TAG_BRANCH" \
$PACKAGE_ROOT
19 changes: 19 additions & 0 deletions db/scripts/common
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -ex

if [[ -v "GITHUB_SHA" ]]; then
GIT_ID=${GITHUB_SHA:0:7}
else
GIT_ID=$(git rev-parse --short=7 HEAD)
fi

if [[ -v "BRANCH_NAME" ]]; then
GIT_BRANCH=${BRANCH_NAME}
else
GIT_BRANCH=$(git symbolic-ref --short HEAD)
fi

ORG=jameel-institute
IMAGE_NAME=daedalus-web-app-db
TAG_SHA="${ORG}/${IMAGE_NAME}:${GIT_ID}"
TAG_BRANCH="${ORG}/${IMAGE_NAME}:${GIT_BRANCH}"
11 changes: 11 additions & 0 deletions db/scripts/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e

HERE=$(dirname $0)
. $HERE/common

docker run --rm -d \
--network=bridge \
--name daedalus-web-app-db \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

daedalus-web-db might save a few characters of pain every time you need to tye this! Or even daedalus-db if there isn't any danger of an R api db container (not sure what the plan is there though)...

Copy link
Contributor Author

@david-mears-2 david-mears-2 Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'll find remembering to omit 'app' more confusing, since I'm generically calling this app 'daedalus-web-app' everywhere, e.g. the github repo, package name. (Didn't want to fully commit to the provisional name 'Daedalus Explore'!)

There will be at least this many Daedalusy 'packages' (broadly defined) to keep track of, so explicit naming should help us steer clear of conflation:

  • web app
  • nginx for web app
  • r api
  • model

-p 5432:5432 \
"$TAG_BRANCH"
19 changes: 19 additions & 0 deletions db/scripts/runQuery
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
set -e

# Check if the SQL query is passed as an argument
if [ -z "$1" ]; then
echo "Usage: $0 \"SQL_QUERY\""
exit 1
fi

# Assign the SQL query to a variable
SQL_QUERY="$1"

# Define the Docker container name, PostgreSQL user, and database
DOCKER_CONTAINER="daedalus-web-app-db"
PG_USER="daedalus-web-app-user"
PG_DATABASE="daedalus-web-app"

# Execute the SQL query using docker exec and psql
docker exec $DOCKER_CONTAINER psql -U $PG_USER -d $PG_DATABASE -c "$SQL_QUERY"
22 changes: 22 additions & 0 deletions lib/prisma.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Although the Prisma nuxt module didn't work (see https://github.com/prisma/nuxt-prisma/issues/22)
// we still need to use the related setup code that creates a global instance of the Prisma Client,
// so this file is a linted version of https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/prisma-nuxt-module#option-b-libprismats

import process from "node:process";
import { PrismaClient } from "@prisma/client";

function prismaClientSingleton() {
return new PrismaClient();
}

declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>
// eslint-disable-next-line no-restricted-globals
} & typeof global;

const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();

export default prisma;

if (process.env.NODE_ENV !== "production")
globalThis.prismaGlobal = prisma;

Check warning on line 22 in lib/prisma.ts

View check run for this annotation

Codecov / codecov/patch

lib/prisma.ts#L2-L22

Added lines #L2 - L22 were not covered by tests
Comment on lines +21 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the Prisma Client is the thing that will be used to query the db? I'm not clear on why a distinction is made for production mode? It looks like in this case the client is expected to already exist?

Copy link
Contributor Author

@david-mears-2 david-mears-2 Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't explain what the production condition is for. The origin of the code is (as commented at top of file) (a linted version of) https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/prisma-nuxt-module#option-b-libprismats

Loading