From 5403e8e3c48b14caa0d08d72f54cee3a4d9cf27a Mon Sep 17 00:00:00 2001 From: Jisu Kim Date: Fri, 4 Aug 2023 15:53:47 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=9A=20=20[Chore]=20development=20?= =?UTF-8?q?=EB=B0=8F=20production=20=ED=99=98=EA=B2=BD=20env=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EB=B6=84=EB=A6=AC=20(#597)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 1 + .env.example | 4 - .gitignore | 1 + backend/.env.example | 14 +- backend/docker-compose.dev.yml | 15 +- backend/package.json | 8 +- backend/seeding/data-source.ts | 40 +++- backend/seeding/reset-db.sh | 11 +- backend/seeding/seeder/message.seeder.ts | 189 ++++++++---------- .../src/config/app/configuration.module.ts | 1 + .../config/database/configuration.module.ts | 11 +- backend/src/config/database/configuration.ts | 10 +- docker-compose.yml | 10 +- 13 files changed, 150 insertions(+), 165 deletions(-) delete mode 100644 .env.example diff --git a/.dockerignore b/.dockerignore index 73ce1c88..eeced9ec 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,7 @@ ./backend/seeding ./backend/yarn-error.log ./backend/test +./backend/.env.development ./frontend/.husky ./frontend/.storybook diff --git a/.env.example b/.env.example deleted file mode 100644 index 61a31316..00000000 --- a/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -# Postgres database -POSTGRES_DB= -POSTGRES_USER= -POSTGRES_PASSWORD= diff --git a/.gitignore b/.gitignore index 4c49bd78..8c208c49 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .env +.env.development diff --git a/backend/.env.example b/backend/.env.example index 95c979d9..5093eb40 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -9,18 +9,16 @@ CLIENT_URL= # Postgres database - docker env 로 사용 # database port (default: 5432) -DB_PORT= +POSTGRES_PORT= # db container name -DB_HOST= +# local - localhost, production - ghostgres +POSTGRES_HOST= # database name -DB_NAME= +POSTGRES_DB= # database user (username 은 실행중인 호스트 머신과 달라도 됩니다.) -DB_USER= +POSTGRES_USER= # database password -DB_PASSWORD= - -# db name for test -TEST_DB_NAME= +POSTGRES_PASSWORD= # 42인트라넷에서 발급받은 API ID FORTYTWO_APP_ID= diff --git a/backend/docker-compose.dev.yml b/backend/docker-compose.dev.yml index 81798f13..37b7feba 100644 --- a/backend/docker-compose.dev.yml +++ b/backend/docker-compose.dev.yml @@ -2,15 +2,12 @@ services: db: container_name: ghostgres_dev image: postgres:15.3-alpine - restart: always - environment: - POSTGRES_DB: ${DB_NAME} - POSTGRES_USER: ${DB_USER} - POSTGRES_PASSWORD: ${DB_PASSWORD} + restart: on-failure:5 + env_file: + - .env.development volumes: - - db_data:/var/lib/postgresql/data + - db_dev_data:/var/lib/postgresql/data ports: - - 8000:5432 - + - ${POSTGRES_PORT}:5432 volumes: - db_data: + db_dev_data: diff --git a/backend/package.json b/backend/package.json index ccceb781..b037584f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,10 +11,10 @@ "start": "nest start", "start:dev": "NODE_ENV=development nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "NODE_ENV=production nest start", - "db": "docker compose --env-file .env -f ./docker-compose.dev.yml up -d", - "db:down": "docker compose --env-file .env -f ./docker-compose.dev.yml down", - "db:reset": "docker compose --env-file .env -f ./docker-compose.dev.yml down -v", + "start:prod": "NODE_ENV=production node dist/backend/src/main", + "db": "docker compose --env-file .env.development -f ./docker-compose.dev.yml up -d", + "db:down": "docker compose --env-file .env.development -f ./docker-compose.dev.yml down", + "db:reset": "docker compose --env-file .env.development -f ./docker-compose.dev.yml down -v", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", diff --git a/backend/seeding/data-source.ts b/backend/seeding/data-source.ts index 631796e8..54d1d012 100644 --- a/backend/seeding/data-source.ts +++ b/backend/seeding/data-source.ts @@ -14,7 +14,9 @@ import { User } from '../src/entity/user.entity'; import seeder from './seeder/message.seeder'; -config(); +config({ + path: '.env.development', +}); (async () => { if (process.argv[2] === undefined) { @@ -25,18 +27,40 @@ config(); const options: DataSourceOptions = { type: 'postgres', - host: process.env.DB_HOST, - port: Number(process.env.DB_PORT), - database: process.env.TEST_DB_NAME, - username: process.env.DB_USER, - password: process.env.DB_PASSWORD, + host: process.env.POSTGRES_HOST, + port: Number(process.env.POSTGRES_PORT), + database: process.env.POSTGRES_DB, + username: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, synchronize: true, entities: [Auth, User, Friendship, Message, MessageView, UserRecord, GameHistory, BlockedUser, Achievement], namingStrategy: new SnakeNamingStrategy(), }; const dataSource = new DataSource(options); - await dataSource.initialize(); - await seeder(dataSource); + try { + await dataSource.initialize(); + } catch (e) { + console.table(e); + console.log(e); + if (e.code === 'ENOTFOUND') { + console.error('Please check the database connection options in the .env file.\n'); + } + await dataSource.destroy(); + process.exit(1); + } + try { + await seeder(dataSource); + } catch (e) { + console.error(e); + if (e.code === '23505') { + console.error( + '\nSeeding data already exists.\nYou can either run "yarn seed reset" to reset the seeding or continue with the development process without performing any seeding.\n', + ); + } + await dataSource.destroy(); + process.exit(1); + } + await dataSource.destroy(); })(); diff --git a/backend/seeding/reset-db.sh b/backend/seeding/reset-db.sh index 6def0b2b..591e5687 100755 --- a/backend/seeding/reset-db.sh +++ b/backend/seeding/reset-db.sh @@ -1,8 +1,9 @@ #!/bin/bash -DB=$(grep "TEST_DB_NAME" .env | cut -d "=" -f 2) -HOST=$(grep "DB_HOST" .env | cut -d "=" -f 2) -PORT=$(grep "DB_PORT" .env | cut -d "=" -f 2) -USER=$(grep "DB_USER" .env | cut -d "=" -f 2) -psql -p $PORT -h $HOST -d postgres -U $USER -c "drop database $DB" -c "create database $DB" +DB=$(grep "POSTGRES_DB" .env.development | head -1 | cut -d "=" -f 2) +HOST=$(grep "POSTGRES_HOST" .env.development | cut -d "=" -f 2) +PORT=$(grep "POSTGRES_PORT" .env.development | cut -d "=" -f 2) +USER=$(grep "POSTGRES_USER" .env.development | cut -d "=" -f 2) +PASSWORD=$(grep "POSTGRES_PASSWORD" .env.development | cut -d "=" -f 2) +PGPASSWORD=$PASSWORD psql -p "$PORT" -h "$HOST" -U "$USER" -d postgres -c "drop database $DB" -c "create database $DB" rm -rf seeding/results; diff --git a/backend/seeding/seeder/message.seeder.ts b/backend/seeding/seeder/message.seeder.ts index 120dd9bd..a5ac7414 100644 --- a/backend/seeding/seeder/message.seeder.ts +++ b/backend/seeding/seeder/message.seeder.ts @@ -1,6 +1,3 @@ -import * as fs from 'fs'; -import * as path from 'path'; - import { faker } from '@faker-js/faker'; import { DataSource, Repository } from 'typeorm'; @@ -17,115 +14,89 @@ import messageFactory from '../factory/message.factory'; import userFactory from '../factory/user.factory'; export default async (dataSource: DataSource) => { - const resultDir = path.join(__dirname, '../results'); - - const authRepository: Repository = dataSource.getRepository(Auth); - const authSeeds = Array(Number(process.argv[2])).fill(null).map(authFactory); - authSeeds[0].status = AuthStatus.REGISTERD; - authSeeds[1].status = AuthStatus.REGISTERD; - authSeeds[2].status = AuthStatus.REGISTERD; - - const auths = await authRepository.save(authSeeds); - console.log('auth ' + auths.length + ' rows created.'); - fs.mkdir(resultDir, () => { - fs.writeFile(path.join(resultDir, 'auths.json'), JSON.stringify(auths), (err) => { - if (err) throw err; - console.log('created auth information has been saved to results/auths.json\n'); - }); - }); - - // generate user - const userRepository = dataSource.getRepository(User); - const users = await userRepository.save(auths.filter((auth) => auth.status === 'REGISTERD').map(userFactory)); - console.log('user ' + users.length + ' rows created.'); - - fs.writeFile(path.join(resultDir, 'users.json'), JSON.stringify(users), (err) => { - if (err) throw err; - console.log('created user information has been saved to seeding/results/users.json\n'); - }); - - // generate user record - const userRecord = users.map((user) => ({ - id: user.id, - })); - - await dataSource.getRepository(UserRecord).save(userRecord); - - // generate friendship - // 1/2 확률로 친구관계 생기게, 그 중 1/2 확률로 accept. - const friendsSeed = []; - for (let i = 0; i < users.length; i++) { - for (let j: number = i + 1; j < users.length; j++) { - if (i == 0 && j == 1) { - friendsSeed.push(frieindshipFactory(users[i], users[j], true, faker.date.past())); - continue; - } else if (i == 0 && j == 2) { - friendsSeed.push(frieindshipFactory(users[i], users[j], false)); - continue; + await dataSource.transaction(async (manager) => { + const authRepository: Repository = manager.getRepository(Auth); + const authSeeds = Array(Number(process.argv[2])).fill(null).map(authFactory); + authSeeds[0].status = AuthStatus.REGISTERD; + authSeeds[1].status = AuthStatus.REGISTERD; + authSeeds[2].status = AuthStatus.REGISTERD; + + const auths = await authRepository.save(authSeeds); + + // generate user + const userRepository = manager.getRepository(User); + const users = await userRepository.save(auths.filter((auth) => auth.status === 'REGISTERD').map(userFactory)); + + // generate user record + const userRecord = users.map((user) => ({ + id: user.id, + })); + + await manager.getRepository(UserRecord).save(userRecord); + + // generate friendship + // 1/2 확률로 친구관계 생기게, 그 중 1/2 확률로 accept. + const friendsSeed = []; + for (let i = 0; i < users.length; i++) { + for (let j: number = i + 1; j < users.length; j++) { + if (i == 0 && j == 1) { + friendsSeed.push(frieindshipFactory(users[i], users[j], true, faker.date.past())); + continue; + } else if (i == 0 && j == 2) { + friendsSeed.push(frieindshipFactory(users[i], users[j], false)); + continue; + } + + const isFirstUserI = faker.datatype.boolean(); + Math.random() <= 0.1 && + friendsSeed.push( + frieindshipFactory(users[isFirstUserI ? i : j], users[isFirstUserI ? j : i], faker.datatype.boolean()), + ); } - - const isFirstUserI = faker.datatype.boolean(); - Math.random() <= 0.1 && - friendsSeed.push( - frieindshipFactory(users[isFirstUserI ? i : j], users[isFirstUserI ? j : i], faker.datatype.boolean()), - ); } - } - - const friendshipRepository = dataSource.getRepository(Friendship); - const friends = await friendshipRepository.save(friendsSeed); - console.log('friendship ' + friends.length + ' rows created.'); - fs.writeFile(path.join(resultDir, 'friends.json'), JSON.stringify(friends), (err) => { - if (err) throw err; - console.log('created friendship information has been saved to seeding/results/friends.json\n'); - }); - - // generate message - // 친구 수락 && 마지막 메세지 시간이 있으면 메세지 생성 - const messageRepository = dataSource.getRepository(Message); - - const messageSeed: Partial[] = []; - friends - .filter((friend) => friend.lastMessageTime !== undefined && friend.accept === true) - .map((friend) => { - const random = Math.floor(Math.random() * 200); - let prevDate: Date | undefined = undefined; - for (let i = 0; i < random; i++) { - const message = messageFactory(friend, prevDate); - messageSeed.push(message); - prevDate = new Date(message.createdAt); + const friendshipRepository = manager.getRepository(Friendship); + const friends = await friendshipRepository.save(friendsSeed); + + // generate message + // 친구 수락 && 마지막 메세지 시간이 있으면 메세지 생성 + const messageRepository = manager.getRepository(Message); + + const messageSeed: Partial[] = []; + friends + .filter((friend) => friend.lastMessageTime !== undefined && friend.accept === true) + .map((friend) => { + const random = Math.floor(Math.random() * 200); + let prevDate: Date | undefined = undefined; + for (let i = 0; i < random; i++) { + const message = messageFactory(friend, prevDate); + messageSeed.push(message); + prevDate = new Date(message.createdAt); + } + }); + + // insert 할 양이 많으면 error 가 발생하므로 100개씩 나눠서 insert + const promises = []; + for (let i = 0; i < messageSeed.length; i += 100) { + promises.push(messageRepository.save(messageSeed.slice(i, i + 100))); + } + await Promise.all(promises); + + const gameSeed = []; + for (let i = 0; i < users.length; i++) { + for (let j: number = i + 1; j < users.length; j++) { + const isFirstUserI = faker.datatype.boolean(); + Math.random() <= 0.1 && + gameSeed.push(gameHistoryFactory(users[isFirstUserI ? i : j], users[isFirstUserI ? j : i])); } - }); - - // insert 할 양이 많으면 error 가 발생하므로 100개씩 나눠서 insert - const promises = []; - for (let i = 0; i < messageSeed.length; i += 100) { - promises.push(messageRepository.save(messageSeed.slice(i, i + 100))); - } - await Promise.all(promises); - - console.log('message ' + messageSeed.length + ' rows created.\n'); - - //fs.writeFile(path.join(resultDir, 'messages.json'), JSON.stringify(messages), (err) => { - // if (err) throw err; - // console.log('created message information has been saved to seeding/results/messages.json\n'); - //}); - - // generate game history - - const gameSeed = []; - for (let i = 0; i < users.length; i++) { - for (let j: number = i + 1; j < users.length; j++) { - const isFirstUserI = faker.datatype.boolean(); - Math.random() <= 0.1 && - gameSeed.push(gameHistoryFactory(users[isFirstUserI ? i : j], users[isFirstUserI ? j : i])); } - } - const gameHistoryRepository = dataSource.getRepository(GameHistory); - await gameHistoryRepository.save(gameSeed); - - console.log('game history ' + gameSeed.length + ' rows created.\n'); - - dataSource.destroy(); + const gameHistoryRepository = manager.getRepository(GameHistory); + await gameHistoryRepository.save(gameSeed); + + console.log('auth ' + auths.length + ' rows created.'); + console.log('user ' + users.length + ' rows created.'); + console.log('friendship ' + friends.length + ' rows created.'); + console.log('message ' + messageSeed.length + ' rows created.\n'); + console.log('game history ' + gameSeed.length + ' rows created.\n'); + }); }; diff --git a/backend/src/config/app/configuration.module.ts b/backend/src/config/app/configuration.module.ts index 4e5a1d08..d8dfe77f 100644 --- a/backend/src/config/app/configuration.module.ts +++ b/backend/src/config/app/configuration.module.ts @@ -8,6 +8,7 @@ import { AppConfigService } from './configuration.service'; @Module({ imports: [ ConfigModule.forRoot({ + envFilePath: ['.env.development', '.env'], load: [configuration], validationSchema: Joi.object({ APP_ENV: Joi.string().valid('development', 'production', 'test').default('development'), diff --git a/backend/src/config/database/configuration.module.ts b/backend/src/config/database/configuration.module.ts index 792712ef..65acf71c 100644 --- a/backend/src/config/database/configuration.module.ts +++ b/backend/src/config/database/configuration.module.ts @@ -8,13 +8,14 @@ import { DatabaseConfigService } from './configuration.service'; @Module({ imports: [ ConfigModule.forRoot({ + envFilePath: ['.env.development', '.env'], load: [configuration], validationSchema: Joi.object({ - DB_HOST: Joi.string(), - DB_PORT: Joi.number(), - DB_NAME: Joi.string(), - DB_USER: Joi.string(), - DB_PASSWORD: Joi.string(), + POSTGRES_HOST: Joi.string(), + POSTGRES_PORT: Joi.number(), + POSTGRES_DB: Joi.string(), + POSTGRES_USER: Joi.string(), + POSTGRES_PASSWORD: Joi.string(), }), }), ], diff --git a/backend/src/config/database/configuration.ts b/backend/src/config/database/configuration.ts index fe22d383..ddd1bbd4 100644 --- a/backend/src/config/database/configuration.ts +++ b/backend/src/config/database/configuration.ts @@ -1,9 +1,9 @@ import { registerAs } from '@nestjs/config'; export default registerAs('database', () => ({ - host: process.env.DB_HOST, - port: process.env.DB_PORT, - name: process.env.DB_NAME, - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, + host: process.env.POSTGRES_HOST, + port: process.env.POSTGRES_PORT, + name: process.env.POSTGRES_DB, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, })); diff --git a/docker-compose.yml b/docker-compose.yml index 1a8d056a..269f580d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,10 +3,8 @@ services: container_name: ghostgres image: postgres:15.3-alpine restart: always - environment: - - POSTGRES_USER - - POSTGRES_PASSWORD - - POSTGRES_DB + env_file: + - ./backend/.env volumes: - db_data:/var/lib/postgresql/data expose: @@ -18,10 +16,6 @@ services: depends_on: - db image: server - environment: - - DB_USER=${POSTGRES_USER} - - DB_PASSWORD=${POSTGRES_PASSWORD} - - DB_NAME=${POSTGRES_DB} build: context: . dockerfile: ./backend/Dockerfile