diff --git a/.gitignore b/.gitignore index a83affec..682a00da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,17 @@ # Include your project-specific ignores in this file # See https://help.github.com/ignore-files/ for more about ignoring files -# Dependencies -/node_modules -/flow-typed -# Testing -/coverage +# Build output +build/ -# Production -/build -/secrets +# Dependencies +node_modules/ +flow-typed/ -# Misc -.firebase/* -__generated__ -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local +# Testing +coverage/ +# Logs npm-debug.log* yarn-debug.log* yarn-error.log* @@ -31,3 +23,13 @@ firebase-error.log* !.vscode/snippets !.vscode/launch.json !.vscode/settings.json + +# Misc +.firebase/* +__generated__ +.DS_Store +.env.local +.env.*.local +.eslintcache +.yarn-integrity +backup.sql diff --git a/README.md b/README.md index bd789008..d550a8a7 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ Also, you need to be familiar with [HTML][html], [CSS][css], [JavaScript][js] ([ ├── node_modules/ # 3rd-party libraries and utilities ├── public/ # Static files such as favicon.ico etc. ├── scripts/ # Automation scripts (yarn update-schema etc.) -├── seeds/ # Reference and seed data for the database ├── src/ # Application source code │ ├── admin/ # Admin section (Dashboard, User Management etc.) │ ├── common/ # Shared React components and HOCs @@ -114,13 +113,13 @@ Then open [http://localhost:3000/](http://localhost:3000/) to see your app.
$ yarn db-change # Create a new database migration file $ yarn db-migrate # Migrate database to the latest version $ yarn db-rollback # Rollback the latest migration -$ yarn db-save # Save data from database to JSON files -$ yarn db-seed # Seed database with previously saved data +$ yarn db-backup --env=prod # Write database backup to backup.sql +$ yarn db-restore # Restore database backup from backup.sql $ yarn db # Open PostgreSQL shell (for testing/debugging) ``` -**Note**: Appending `--env=prod`, `--env=test` flags to any of the commands above will force it to -use database connection settings from `.env.production` and/or `.env.test` file(s). +**Note**: Appending `--env=prod` or `--env=test` flags to any of the commands above will force it +to use database connection settings from `.env.production` or `.env.test` files. ### How to Test @@ -134,7 +133,7 @@ $ yarn test # Run unit tests. Or, `yarn test -- --watch` 1. Create a new **Google Cloud** project and **Cloud SQL** database. 2. Configure authentication in **Firebase** dashboard. -3. Set Firebase project ID in `.firebaserc` file. +3. Set Google Cloud project ID in `package.json` file (see `scripts`). 4. Set API keys, secrets and other settings in `.env.production` file. 5. Migrate the database by running `yarn db-migrate --env=prod`. 6. Finally, deploy your application by running `yarn deploy-prod`. diff --git a/package.json b/package.json index 0abfafb2..642b3099 100644 --- a/package.json +++ b/package.json @@ -96,9 +96,9 @@ } }, "scripts": { + "setup": "node ./scripts/setup", "update-schema": "node ./scripts/update-schema", "relay": "relay-compiler --src ./src --schema ./schema.graphql", - "setup": "node ./scripts/setup", "prestart": "yarn relay", "start": "react-app start", "build": "react-app build", @@ -106,7 +106,8 @@ "lint": "eslint --ignore-path .gitignore --ignore-pattern \"!**/.*\" .", "lint-fix": "eslint --ignore-path .gitignore --ignore-pattern \"!**/.*\" --fix . && yarn run prettier --write \"**/*.{js,json}\"", "db": "node ./scripts/db", - "db-save": "node ./scripts/db-save", + "db-backup": "node ./scripts/db-backup", + "db-restore": "node ./scripts/db-restore", "db-change": "knex migrate:make", "db-migrate": "knex migrate:latest", "db-rollback": "knex migrate:rollback", diff --git a/scripts/db-backup.js b/scripts/db-backup.js new file mode 100644 index 00000000..133d2371 --- /dev/null +++ b/scripts/db-backup.js @@ -0,0 +1,87 @@ +/** + * React Starter Kit for Firebase + * https://github.com/kriasoft/react-firebase-starter + * Copyright (c) 2015-present Kriasoft | MIT License + */ + +const fs = require('fs'); +const readline = require('readline'); +const cp = require('child_process'); +const { EOL } = require('os'); + +// Load environment variables (PGHOST, PGUSER, etc.) +require('../knexfile'); + +// Ensure that the SSL key file has correct permissions +if (process.env.PGSSLKEY) { + cp.spawnSync('chmod', ['0600', process.env.PGSSLKEY], { stdio: 'inherit' }); +} + +// Get the list of database tables +let cmd = cp.spawnSync( + 'psql', + [ + '--no-align', + '--tuples-only', + '--record-separator=|', + '--command', + "SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE'", + ], + { + stdio: ['inherit', 'pipe', 'inherit'], + }, +); + +if (cmd.status !== 0) { + console.error('Failed to read the list of database tables.'); + process.exit(cmd.status); +} + +const tables = cmd.stdout + .toString('utf8') + .trim() + .split('|') + .filter(x => x !== 'migrations' && x !== 'migrations_lock') + .map(x => `public."${x}"`) + .join(', '); + +// Dump the database +cmd = cp + .spawn( + 'pg_dump', + [ + '--data-only', + '--no-owner', + '--no-privileges', + '--column-inserts', + '--disable-triggers', + '--exclude-table=migrations', + '--exclude-table=migrations_lock', + '--exclude-table=migrations_id_seq', + '--exclude-table=migrations_lock_index_seq', + ...process.argv.slice(2).filter(x => !x.startsWith('--env')), + ], + { + stdio: ['pipe', 'pipe', 'inherit'], + }, + ) + .on('exit', code => { + if (code !== 0) process.exit(code); + }); + +const out = fs.createWriteStream('backup.sql', { encoding: 'utf8' }); +const rl = readline.createInterface({ input: cmd.stdout, terminal: false }); + +rl.on('line', line => { + // Some (system) triggers cannot be disabled in a cloud environment + // "DISABLE TRIGGER ALL" => "DISABLE TRIGGER USER" + if (line.endsWith(' TRIGGER ALL;')) { + out.write(`${line.substr(0, line.length - 5)} USER;${EOL}`, 'utf8'); + } + // Add a command that truncates all the database tables + else if (line.startsWith('SET row_security')) { + out.write(`${line}${EOL}${EOL}TRUNCATE TABLE ${tables} CASCADE;${EOL}`); + } else { + out.write(`${line}${EOL}`, 'utf8'); + } +}); diff --git a/scripts/db-restore.js b/scripts/db-restore.js new file mode 100644 index 00000000..96422502 --- /dev/null +++ b/scripts/db-restore.js @@ -0,0 +1,28 @@ +/** + * React Starter Kit for Firebase + * https://github.com/kriasoft/react-firebase-starter + * Copyright (c) 2015-present Kriasoft | MIT License + */ + +const cp = require('child_process'); + +// Load environment variables (PGHOST, PGUSER, etc.) +require('../knexfile'); + +// Ensure that the SSL key file has correct permissions +if (process.env.PGSSLKEY) { + cp.spawnSync('chmod', ['0600', process.env.PGSSLKEY], { stdio: 'inherit' }); +} + +cp.spawn( + 'psql', + [ + '--file=backup.sql', + '--echo-errors', + '--no-readline', + ...process.argv.slice(2).filter(x => !x.startsWith('--env')), + ], + { + stdio: 'inherit', + }, +).on('exit', process.exit);