diff --git a/Dockerfile b/Dockerfile index 7796eb23..e8cdc729 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,4 +52,4 @@ EXPOSE 3000 ENV PORT 3000 -CMD ["yarn", "production"] +CMD ["yarn", "start-docker"] diff --git a/README.md b/README.md index 3a14a010..224713d1 100644 --- a/README.md +++ b/README.md @@ -51,15 +51,14 @@ mysql://username:mypassword@localhost:3306/mydb The `HASH_SALT` is used to generate unique values for your installation. -### Check database +This will also create a login account with username **admin** and password **umami**. + +### Create database tables ```bash -yarn check-db +yarn update-db ``` -The database structure will automatically be applied on the first start of Umami. -This will also create a login account with username **admin** and password **umami**. - ### Build the application ```bash @@ -102,6 +101,7 @@ To get the latest features, simply do a pull, install any new dependencies, and git pull yarn install yarn build +yarn update-db ``` To update the Docker image, simply pull the new images and rebuild: diff --git a/package.json b/package.json index f5386cb5..684c4374 100644 --- a/package.json +++ b/package.json @@ -12,19 +12,20 @@ "scripts": { "dev": "next dev", "build": "npm-run-all build-tracker build-geo build-db build-app", - "start": "next start", + "start": "npm-run-all check-db start-next", + "start-docker": "npm-run-all check-db start-server", "start-env": "node -r dotenv/config scripts/start-env.js", "start-server": "node server.js", + "start-next": "next start", "build-app": "next build", "build-tracker": "rollup -c rollup.tracker.config.js", - "build-db": "npm-run-all copy-db-files build-db-client", + "build-db": "npm-run-all copy-db-files build-db-client check-db", "build-lang": "npm-run-all format-lang compile-lang", "build-geo": "node scripts/build-geo.js", "build-db-schema": "prisma db pull", "build-db-client": "prisma generate", - "test-db": "node scripts/test-db.js", - "check-db": "prisma migrate status", "update-db": "prisma migrate deploy", + "check-db": "node scripts/check-db.js", "copy-db-files": "node scripts/copy-db-files.js", "generate-lang": "npm-run-all extract-lang merge-lang", "extract-lang": "formatjs extract \"{pages,components}/**/*.js\" --out-file build/messages.json", diff --git a/scripts/check-db.js b/scripts/check-db.js index 118ebd21..2e27a838 100644 --- a/scripts/check-db.js +++ b/scripts/check-db.js @@ -1,16 +1,94 @@ require('dotenv').config(); const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); +const chalk = require('chalk'); +const spawn = require('cross-spawn'); -async function check() { - await prisma.account.findMany({ limit: 1 }); +let message = ''; + +function success(msg) { + console.log(chalk.greenBright(`✓ ${msg}`)); } -check() - .catch(e => { - console.error(e); - process.exit(1); - }) - .finally(async () => { - await prisma.$disconnect(); +async function checkEnv() { + if (!process.env.DATABASE_URL) { + throw new Error('DATABASE_URL is not defined.'); + } else { + success('DATABASE_URL is defined.'); + } +} + +async function checkConnection() { + try { + await prisma.$connect(); + + success('Database connection successful.'); + } catch (e) { + throw new Error('Unable to connect to the database.'); + } +} + +async function checkTables() { + try { + await prisma.account.findFirst(); + + success('Database tables found.'); + } catch (e) { + message = `To update your database, you need to run:\n${chalk.bold.whiteBright( + 'yarn update-db', + )}`; + throw new Error('Database tables not found.'); + } +} + +async function run(cmd, args) { + const buffer = []; + const proc = spawn(cmd, args); + + return new Promise((resolve, reject) => { + proc.stdout.on('data', data => buffer.push(data)); + + proc.on('error', () => { + reject(new Error('Failed to run Prisma.')); + }); + + proc.on('exit', () => resolve(buffer.join(''))); }); +} + +async function checkMigrations() { + const output = await run('prisma', ['migrate', 'status']); + + const missingMigrations = output.includes('Following migration have not yet been applied'); + const notManaged = output.includes('The current database is not managed'); + + if (notManaged) { + const cmd = output.match(/yarn prisma migrate resolve --applied ".*"/g); + + message = `You need to update your database by running:\n${chalk.bold.whiteBright(cmd[0])}`; + throw new Error('Database is out of date.'); + } else if (missingMigrations) { + message = output; + throw new Error('Database is out of date.'); + } + + success('Database is up to date.'); +} + +(async () => { + let err = false; + for (let fn of [checkEnv, checkConnection, checkTables, checkMigrations]) { + try { + await fn(); + } catch (e) { + console.log(chalk.red(`✗ ${e.message}`)); + err = true; + } finally { + await prisma.$disconnect(); + if (err) { + console.log(message); + process.exit(1); + } + } + } +})(); diff --git a/scripts/copy-db-files.js b/scripts/copy-db-files.js index 9bba914d..92d5c49f 100644 --- a/scripts/copy-db-files.js +++ b/scripts/copy-db-files.js @@ -2,7 +2,7 @@ require('dotenv').config(); const fse = require('fs-extra'); const path = require('path'); -function getDatabase() { +function getDatabaseType() { const type = process.env.DATABASE_TYPE || (process.env.DATABASE_URL && process.env.DATABASE_URL.split(':')[0]); @@ -14,7 +14,7 @@ function getDatabase() { return type; } -const databaseType = getDatabase(); +const databaseType = getDatabaseType(); if (!databaseType || !['mysql', 'postgresql'].includes(databaseType)) { throw new Error('Missing or invalid database'); diff --git a/yarn.lock b/yarn.lock index c6054736..6478c811 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2246,25 +2246,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001283: - version "1.0.30001314" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz#65c7f9fb7e4594fca0a333bec1d8939662377596" - integrity sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw== - -caniuse-lite@^1.0.30001313: - version "1.0.30001320" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz#8397391bec389b8ccce328636499b7284ee13285" - integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA== - -caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001335: - version "1.0.30001344" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz#8a1e7fdc4db9c2ec79a05e9fd68eb93a761888bb" - integrity sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g== - -caniuse-lite@^1.0.30001349: - version "1.0.30001352" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz#cc6f5da3f983979ad1e2cdbae0505dccaa7c6a12" - integrity sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA== +caniuse-lite@^1.0.30001283, caniuse-lite@^1.0.30001313, caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001349: + version "1.0.30001356" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001356.tgz" + integrity sha512-/30854bktMLhxtjieIxsrJBfs2gTM1pel6MXKF3K+RdIVJZcsn2A2QdhsuR4/p9+R204fZw0zCBBhktX8xWuyQ== chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2"