Skip to content

Commit

Permalink
postgres migrations, docker-compose.local-postgres.yml
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkeksz committed Jan 7, 2024
1 parent ac7bf30 commit eb9abeb
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 29 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Docker
Dockerfile
.dockerignore
docker-compose.local-postgres.yml

# Compiled output
/dist
node_modules
error-graph.json
schema.gql
/temp-postgres-data

# Logs
logs
Expand Down
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ POSTGRES_DATABASE=
# Default: false
POSTGRES_SYNCHRONIZE=false

# Description: PostgreSQL migrations run. Not for production!
# Description: PostgreSQL migrations run while the application is starting.
# Type: boolean
# Default: false
POSTGRES_MIGRATIONS_RUN=false
Expand Down
6 changes: 3 additions & 3 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ POSTGRES_USERNAME=waisy
POSTGRES_PASSWORD=1234
POSTGRES_DATABASE=waisy
POSTGRES_HOST=localhost
POSTGRES_PORT=5431
POSTGRES_SYNCHRONIZE=true
POSTGRES_MIGRATIONS_RUN=false
POSTGRES_PORT=5444
POSTGRES_SYNCHRONIZE=false
POSTGRES_MIGRATIONS_RUN=true
EMAIL_VERIFICATION_CODE_LIFETIME_MINUTES=10
EMAIL_VERIFICATION_CODE_MAX_SENDING_ATTEMPTS=3
EMAIL_VERIFICATION_CODE_MAX_INPUT_ATTEMPTS=3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/node_modules
error-graph.json
schema.gql
/temp-postgres-data

# Logs
logs
Expand Down
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ USER node

RUN npm run graphql:generate-schema

RUN npm run build:webpack
RUN npm run build

RUN npm ci --omit=dev && npm cache clean --force

Expand All @@ -45,7 +45,6 @@ WORKDIR /usr/src/app
# Copy the bundled code from the build stage to the production image
COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
COPY --chown=node:node --from=build /usr/src/app/dist ./dist
COPY --chown=node:node --from=build /usr/src/app/.env ./

USER node

Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,26 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
## License

Nest is [MIT licensed](LICENSE).

# Requirements

- Postgres 16.1
- Node.js 20.10.0

# Docker compose postgres
`docker compose -f docker-compose.local-postgres.yml up -d`

# Generate new migration
1. clear the postgres database of the application
2. `npm run migration:generate`

# Create new migration
1. clear the postgres database of the application
2. `npm run migration:create`
3. write code to the created migration file

# Run migration
1. `npm run migration:run`

# Revert migration
1. `npm run migration:revert`
32 changes: 32 additions & 0 deletions docker-compose.local-postgres.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
version: "3.1"
services:
waisy_postgres_db_dev:
image: postgres:16.1
ports:
- '5444:5432'
container_name: 'waisy_postgres_db_dev'
volumes:
- ./temp-postgres-data/dev-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: "waisy"
POSTGRES_USER: "waisy"
POSTGRES_PASSWORD: "1234"
POSTGRES_HOST_AUTH_METHOD: "trust"
LANG: 'C'
LANGUAGE: 'C'
LC_ALL: 'C'
waisy_postgres_db_test:
image: postgres:16.1
ports:
- '5443:5432'
container_name: 'waisy_postgres_db_test'
volumes:
- ./temp-postgres-data/test-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: "waisy"
POSTGRES_USER: "waisy"
POSTGRES_PASSWORD: "1234"
POSTGRES_HOST_AUTH_METHOD: "trust"
LANG: 'C'
LANGUAGE: 'C'
LC_ALL: 'C'
40 changes: 40 additions & 0 deletions ormconfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as dotenv from 'dotenv'
import {DataSource} from 'typeorm'
import * as fs from 'fs'
import * as Joi from 'joi'
import {buildLoggerInstance} from './src/logger'
import {buildDataSourceOptions} from './src/type-orm/type-orm.module.options'
import {postgresConfigEnvValidationSchema} from './src/config/postgres/postgres.config.env-validation-schema'

const hasLocalEnvFile = fs.existsSync('.env.local')
dotenv.config({
override: false,
path: hasLocalEnvFile ? '.env.local' : '.env',
})

function buildOrmConfig(): DataSource {
const logger = buildLoggerInstance('warn')

const {error, value, warning} = Joi.object(postgresConfigEnvValidationSchema).validate(
process.env,
{stripUnknown: true},
)

if (error) {
logger.error(error)
throw new Error(`Config validation error: ${error}`)
}
if (warning) logger.warn(warning)

return new DataSource(
buildDataSourceOptions({
host: value.POSTGRES_HOST,
port: Number(value.POSTGRES_PORT),
username: value.POSTGRES_USERNAME,
password: value.POSTGRES_PASSWORD,
database: value.POSTGRES_DATABASE,
}),
)
}

export default buildOrmConfig()
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"build:webpack": "nest build --webpack --path tsconfig.build-webpack.json",
"graphql:generate-schema": "ts-node src/schema-generator.script.ts",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
Expand All @@ -21,7 +20,11 @@
"test:watch": "NODE_ENV=test jest --watch",
"test:cov": "NODE_ENV=test jest --coverage",
"test:debug": "NODE_ENV=test node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "NODE_ENV=test jest --config ./test/jest-e2e.json --runInBand"
"test:e2e": "NODE_ENV=test jest --config ./test/jest-e2e.json --runInBand",
"migration:generate": "typeorm-ts-node-commonjs -d ormconfig.ts -p true migration:generate src/migrations/migration",
"migration:create": "typeorm-ts-node-commonjs migration:create src/migrations/migration -- -d ormconfig.ts",
"migration:run": "typeorm-ts-node-commonjs migration:run -d ormconfig.ts",
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d ormconfig.ts"
},
"engines": {
"node": ">=20.10.0"
Expand All @@ -42,6 +45,7 @@
"axios": "^1.6.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"dotenv": "^16.3.1",
"graphql": "^16.8.1",
"graphql-query-complexity": "^0.12.0",
"http-status-codes": "^2.3.0",
Expand Down
1 change: 1 addition & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {ValidationModule} from './validation/validation.module'
import {GraphqlModule} from './graphql/graphql.module'
import {CryptModule} from './crypt/crypt.module'
import {TypeOrmModule} from './type-orm/type-orm.module'
// TODO: узнать почему не работают e2e тесты локально
// TODO: переписать тесты под нативный test_runner
@Module({
imports: [
Expand Down
143 changes: 143 additions & 0 deletions src/migrations/1704656438060-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {MigrationInterface, QueryRunner} from 'typeorm'

export class Migration1704656438060 implements MigrationInterface {
name = 'Migration1704656438060'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TYPE "public"."email_verification_code_input_attempt_status_enum" AS ENUM('success', 'failure')
`)
await queryRunner.query(`
CREATE TABLE "email_verification_code_input_attempt" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"email" character varying NOT NULL,
"senderIp" inet NOT NULL,
"status" "public"."email_verification_code_input_attempt_status_enum" NOT NULL,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
CONSTRAINT "PK_8d50b1c50ee28684f3207e9f560" PRIMARY KEY ("id")
);
COMMENT ON COLUMN "email_verification_code_input_attempt"."email" IS 'Email address that the code was sent to';
COMMENT ON COLUMN "email_verification_code_input_attempt"."senderIp" IS 'The IP address of the client that entered the code';
COMMENT ON COLUMN "email_verification_code_input_attempt"."status" IS 'The status of the verification attempt'
`)
await queryRunner.query(`
CREATE TABLE "email_verification_code_sending_attempt" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"email" character varying NOT NULL,
"senderIp" inet NOT NULL,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
CONSTRAINT "PK_a5a612a41a1b640702da6638861" PRIMARY KEY ("id")
);
COMMENT ON COLUMN "email_verification_code_sending_attempt"."email" IS 'Email address to which the code was sent';
COMMENT ON COLUMN "email_verification_code_sending_attempt"."senderIp" IS 'The IP address of the client that requested the code'
`)
await queryRunner.query(`
CREATE TABLE "user" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"email" character varying NOT NULL,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"),
CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id")
)
`)
await queryRunner.query(`
CREATE TYPE "public"."refresh_token_status_enum" AS ENUM('active', 'inactive')
`)
await queryRunner.query(`
CREATE TABLE "refresh_token" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"status" "public"."refresh_token_status_enum" NOT NULL DEFAULT 'active',
"refreshToken" character varying NOT NULL,
"deviceInfo" character varying NOT NULL,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"userId" uuid NOT NULL,
CONSTRAINT "UQ_428e14ded7299edfcf58918beaf" UNIQUE ("refreshToken"),
CONSTRAINT "PK_b575dd3c21fb0831013c909e7fe" PRIMARY KEY ("id")
);
COMMENT ON COLUMN "refresh_token"."status" IS 'Status of token';
COMMENT ON COLUMN "refresh_token"."refreshToken" IS 'Hashed refresh token';
COMMENT ON COLUMN "refresh_token"."deviceInfo" IS 'Device information'
`)
await queryRunner.query(`
CREATE UNIQUE INDEX "IDX_c98a57bf5874dc146aa6360fd0" ON "refresh_token" ("userId", "deviceInfo")
WHERE "status" = 'active'
`)
await queryRunner.query(`
CREATE TYPE "public"."email_verification_code_status_enum" AS ENUM('active', 'expired', 'used')
`)
await queryRunner.query(`
CREATE TABLE "email_verification_code" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"code" integer NOT NULL,
"status" "public"."email_verification_code_status_enum" NOT NULL DEFAULT 'active',
"expirationDate" TIMESTAMP WITH TIME ZONE NOT NULL,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"userId" uuid NOT NULL,
CONSTRAINT "PK_7fc72ac16aeeab466c48748221c" PRIMARY KEY ("id")
);
COMMENT ON COLUMN "email_verification_code"."code" IS 'The verification code sent via email';
COMMENT ON COLUMN "email_verification_code"."status" IS 'Status of the verification code'
`)
await queryRunner.query(`
ALTER TABLE "refresh_token"
ADD CONSTRAINT "FK_8e913e288156c133999341156ad" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION
`)
await queryRunner.query(`
ALTER TABLE "email_verification_code"
ADD CONSTRAINT "FK_cace043f9e8bee80c2dd5c66ccc" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION
`)
await queryRunner.query(`
CREATE TABLE "query-result-cache" (
"id" SERIAL NOT NULL,
"identifier" character varying,
"time" bigint NOT NULL,
"duration" integer NOT NULL,
"query" text NOT NULL,
"result" text NOT NULL,
CONSTRAINT "PK_6a98f758d8bfd010e7e10ffd3d3" PRIMARY KEY ("id")
)
`)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP TABLE "query-result-cache"
`)
await queryRunner.query(`
ALTER TABLE "email_verification_code" DROP CONSTRAINT "FK_cace043f9e8bee80c2dd5c66ccc"
`)
await queryRunner.query(`
ALTER TABLE "refresh_token" DROP CONSTRAINT "FK_8e913e288156c133999341156ad"
`)
await queryRunner.query(`
DROP TABLE "email_verification_code"
`)
await queryRunner.query(`
DROP TYPE "public"."email_verification_code_status_enum"
`)
await queryRunner.query(`
DROP INDEX "public"."IDX_c98a57bf5874dc146aa6360fd0"
`)
await queryRunner.query(`
DROP TABLE "refresh_token"
`)
await queryRunner.query(`
DROP TYPE "public"."refresh_token_status_enum"
`)
await queryRunner.query(`
DROP TABLE "user"
`)
await queryRunner.query(`
DROP TABLE "email_verification_code_sending_attempt"
`)
await queryRunner.query(`
DROP TABLE "email_verification_code_input_attempt"
`)
await queryRunner.query(`
DROP TYPE "public"."email_verification_code_input_attempt_status_enum"
`)
}
}
Loading

0 comments on commit eb9abeb

Please sign in to comment.