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

✨ [Feature] database seeding #96

Merged
merged 6 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ DB_PORT=
DB_USER=
DB_PASSWORD=
DB_HOST=

# db name for test
TEST_DB_NAME=
3 changes: 3 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# env files
.env

# seeding result
/seeding/results

# compiled output
/dist
/node_modules
Expand Down
6 changes: 5 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"db": "docker compose --env-file .env -f ../docker-compose.yml up -d",
"db:down": "docker compose --env-file .env -f ../docker-compose.yml down",
"db:reset": "docker compose --env-file .env -f ../docker-compose.yml down -v",
"postinstall": "if [ ! -d .husky/_ ]; then cd .. && husky install backend/.husky; fi"
"postinstall": "if [ ! -d .husky/_ ]; then cd .. && husky install backend/.husky; fi",
"seed": "npx ts-node ./seeding/data-source.ts",
"seed:reset": "./seeding/reset-db.sh"
},
"dependencies": {
"@hapi/joi": "^17.1.1",
Expand All @@ -43,10 +45,12 @@
"typeorm-naming-strategies": "^4.1.0"
},
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.4.0",
"@types/express": "^4.17.13",
"@types/faker": "^6.6.9",
"@types/hapi__joi": "^17.1.9",
"@types/jest": "29.2.4",
"@types/node": "18.11.18",
Expand Down
41 changes: 41 additions & 0 deletions backend/seeding/data-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//import dotenv
Copy link
Contributor

Choose a reason for hiding this comment

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

이상한 주석이 있어요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Screenshot 2023-04-19 at 11 28 31 AM

다음 pr 에 반영하겠습니다 ^^

import { config } from 'dotenv';
import { DataSource, DataSourceOptions } from 'typeorm';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';

import { Achievement } from '../src/entity/achievement.entity';
import { Auth } from '../src/entity/auth.entity';
import { BlockedUser } from '../src/entity/blocked-user.entity';
import { Friendship } from '../src/entity/friendship.entity';
import { GameHistory } from '../src/entity/game-history.entity';
import { MessageView } from '../src/entity/message-view.entity';
import { Message } from '../src/entity/message.entity';
import { UserRecord } from '../src/entity/user-record.entity';
import { User } from '../src/entity/user.entity';

import seeder from './seeder/message.seeder';

config();

(async () => {
if (process.argv[2] === undefined) {
console.log('Please enter the number of auths to be created.');
}

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,
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);
})();
11 changes: 11 additions & 0 deletions backend/seeding/factory/auth.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// file name should be <entityName>.factory.ts

import { faker } from '@faker-js/faker';

import { AuthStatus } from '../../src/entity/auth.entity';

export default () => ({
//auth.id is auto generated
status: faker.datatype.boolean() && faker.datatype.boolean() ? AuthStatus.UNREGISTERD : AuthStatus.REGISTERD,
email: faker.internet.email(),
});
15 changes: 15 additions & 0 deletions backend/seeding/factory/frieindship.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// file name should be <entityName>.factory.ts

import { faker } from '@faker-js/faker';

import { User } from '../../src/entity/user.entity';

export default (user1: User, user2: User) => {
const accept = faker.datatype.boolean();
return {
sender: user1,
receiver: user2,
accept,
lastMessegeTime: accept && faker.datatype.boolean() ? faker.date.past() : undefined,
};
};
14 changes: 14 additions & 0 deletions backend/seeding/factory/message.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// file name should be <entityName>.factory.ts

import { faker } from '@faker-js/faker';

import { Friendship } from '../../src/entity/friendship.entity';

export default (friendship: Friendship) => {
return {
sender: faker.datatype.boolean() ? friendship.sender : friendship.receiver,
friend: friendship,
contents: faker.lorem.sentence(),
createdAt: faker.date.past(1, friendship.lastMessegeTime),
};
};
14 changes: 14 additions & 0 deletions backend/seeding/factory/user.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// file name should be <entityName>.factory.ts

import { faker } from '@faker-js/faker';

import { Auth } from '../../src/entity/auth.entity';

export default (auth: Auth) => {
return {
id: auth.id,
nickname: faker.helpers.unique(faker.word.noun, [{ length: { min: 3, max: 8 } }]),
exp: faker.datatype.number(100000),
image: faker.image.imageUrl(),
};
};
4 changes: 4 additions & 0 deletions backend/seeding/reset-db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
DB=$(grep "TEST_DB_NAME" .env | cut -d "=" -f 2)
dropdb $DB -f 2>/dev/null; createdb $DB;
rm -rf seeding/results;
85 changes: 85 additions & 0 deletions backend/seeding/seeder/message.seeder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as fs from 'fs';
import * as path from 'path';

import { DataSource, Repository } from 'typeorm';

import { Auth } from '../../src/entity/auth.entity';
import { Friendship } from '../../src/entity/friendship.entity';
import { Message } from '../../src/entity/message.entity';
import { User } from '../../src/entity/user.entity';
import authFactory from '../factory/auth.factory';
import frieindshipFactory from '../factory/frieindship.factory';
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<Auth> = dataSource.getRepository(Auth);
const auths = await authRepository.save(Array(Number(process.argv[2])).fill(null).map(authFactory));
//const auths = await factoryManager.get(Auth).saveMany(Number(process.argv[2]));
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 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++) {
Math.random() <= 0.1 && friendsSeed.push(frieindshipFactory(users[i], users[j]));
}
}

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<Message>[] = [];
friends
.filter((friend) => friend.lastMessegeTime !== undefined && friend.accept === true)
.map((friend) => {
const random = Math.floor(Math.random() * 200);
for (let i = 0; i < random; i++) {
messageSeed.push(messageFactory(friend));
}
});

const promises = [];
for (let i = 0; i < messageSeed.length; i += 10) {
promises.push(messageRepository.save(messageSeed.slice(i, i + 10)));
}
const messages = await Promise.all(promises);

console.log('message ' + messages.length + ' rows created.');

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');
});

dataSource.destroy();
};
3 changes: 0 additions & 3 deletions backend/src/common/README.md

This file was deleted.

17 changes: 17 additions & 0 deletions backend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d"
integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==

"@faker-js/faker@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-7.6.0.tgz#9ea331766084288634a9247fcd8b84f16ff4ba07"
integrity sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==

"@hapi/address@^4.0.1":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.1.0.tgz#d60c5c0d930e77456fdcde2598e77302e2955e1d"
Expand Down Expand Up @@ -1011,6 +1016,13 @@
"@types/qs" "*"
"@types/serve-static" "*"

"@types/faker@^6.6.9":
version "6.6.9"
resolved "https://registry.yarnpkg.com/@types/faker/-/faker-6.6.9.tgz#1064e7c46be58388fa326e2f918a4f02ab740a7a"
integrity sha512-Y9YYm5L//8ooiiknO++4Gr539zzdI0j3aXnOBjo1Vk+kTvffY10GuE2wn78AFPECwZ5MYGTjiDVw1naLLdDimw==
dependencies:
faker "*"

"@types/graceful-fs@^4.1.3":
version "4.1.6"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae"
Expand Down Expand Up @@ -2723,6 +2735,11 @@ external-editor@^3.0.3:
iconv-lite "^0.4.24"
tmp "^0.0.33"

faker@*:
version "6.6.6"
resolved "https://registry.yarnpkg.com/faker/-/faker-6.6.6.tgz#e9529da0109dca4c7c5dbfeaadbd9234af943033"
integrity sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==

fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
Expand Down