Skip to content

Commit

Permalink
Merge pull request #304 from TheCodeGhinux/chore/db
Browse files Browse the repository at this point in the history
chore: database schema and typeorm setup
  • Loading branch information
buka4rill authored Jul 21, 2024
2 parents b9c41bf + 3d093b5 commit 80358ad
Show file tree
Hide file tree
Showing 14 changed files with 1,436 additions and 135 deletions.
1,115 changes: 1,005 additions & 110 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,34 @@
"test:e2e": "jest --config ./test/jest-e2e.json",
"prepare": "husky install",
"check-format": "prettier --check .",
"check-lint": "eslint . --ext ts --ext tsx --ext js"
"check-lint": "eslint . --ext ts --ext tsx --ext js",
"typeorm": "npx typeorm -- -d dist/DB/migrate.ts",
"migration:create": "npx typeorm migration:create db/migrations/migration",
"premigration:generate": "npm run build",
"migration:generate": "npm run build && npx typeorm migration:generate db/migrations/migration -d dist/src/database/data-source",
"premigration:run": "npm run build",
"migration:run": "npx typeorm migration:run -d dist/src/database/data-source",
"migration:revert": "npx typeorm migration:revert -d dist/src/database/data-source",
"seed": "ts-node src/seed.ts"
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.2",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.3.9",
"@nestjs/swagger": "^7.3.1",
"@nestjs/typeorm": "^10.0.2",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"joi": "^17.6.0",
"nestjs-pino": "^4.1.0",
"reflect-metadata": "^0.2.0",
"pg": "^8.12.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"typeorm": "^0.3.20",
"typeorm-extension": "^3.5.1",
"types-joi": "^2.1.0"
},
"devDependencies": {
Expand All @@ -43,6 +57,7 @@
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/bcrypt": "^5.0.2",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
Expand Down
23 changes: 11 additions & 12 deletions src/app.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import HealthController from "./health.controller";
import HealthController from './health.controller';

describe('Health Check Test', () => {
let healthController: HealthController;
let healthController: HealthController;

beforeEach(() => {
healthController = new HealthController();
});
beforeEach(() => {
healthController = new HealthController();
});

describe('Get Health endpoint', () => {
it('should return healthy endpoint', async () => {
const result = 'healthy endpoint';
describe('Get Health endpoint', () => {
it('should return healthy endpoint', async () => {
const result = 'healthy endpoint';

expect(await healthController.health()).toBe(result);
});
expect(await healthController.health()).toBe(result);
});

});
});
});
14 changes: 12 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { APP_PIPE } from '@nestjs/core';
import serverConfig from '../config/server.config';
import * as Joi from 'joi';
import { LoggerModule } from 'nestjs-pino';
import { TypeOrmModule } from '@nestjs/typeorm';
import dataSource from './database/data-source';
import { SeedingModule } from './database/seeding/seeding.module';
import HealthController from './health.controller';

@Module({
Expand Down Expand Up @@ -42,7 +45,14 @@ import HealthController from './health.controller';
}),
}),
LoggerModule.forRoot(),
TypeOrmModule.forRootAsync({
useFactory: async () => ({
...dataSource.options,
}),
dataSourceFactory: async () => dataSource,
}),
SeedingModule,
],
controllers: [HealthController]
controllers: [HealthController],
})
export class AppModule { }
export class AppModule {}
28 changes: 28 additions & 0 deletions src/database/data-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { DataSource, DataSourceOptions } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import * as dotenv from 'dotenv';

dotenv.config();

const isDevelopment = process.env.NODE_ENV === 'development';

const dataSource = new DataSource({
type: process.env.DB_TYPE as 'postgres',
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
database: process.env.DB_DATABASE,
entities: [process.env.DB_ENTITIES],
migrations: [process.env.DB_MIGRATIONS],
synchronize: isDevelopment,
migrationsTableName: 'migrations',
});

export async function initializeDataSource() {
if (!dataSource.isInitialized) {
await dataSource.initialize();
}
return dataSource;
}

export default dataSource;
33 changes: 33 additions & 0 deletions src/database/seeding/seeding.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Controller, Get, Post } from '@nestjs/common';
import { SeedingService } from './seeding.service';

@Controller('seed')
export class SeedingController {
constructor(private readonly seedingService: SeedingService) {}

@Post()
async seedDatabase() {
await this.seedingService.seedDatabase();
return { message: 'Database seeding initiated' };
}

@Get('users')
async getUsers() {
return this.seedingService.getUsers();
}

@Get('profiles')
async getProfiles() {
return this.seedingService.getProfiles();
}

@Get('products')
async getProducts() {
return this.seedingService.getProducts();
}

@Get('organisations')
async getOrganisations() {
return this.seedingService.getOrganisations();
}
}
15 changes: 15 additions & 0 deletions src/database/seeding/seeding.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { SeedingService } from './seeding.service';
import { SeedingController } from './seeding.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'src/entities/user.entity';
import { Profile } from 'src/entities/profile.entity';
import { Product } from 'src/entities/product.entity';
import { Organisation } from 'src/entities/organisation.entity';

@Module({
imports: [TypeOrmModule.forFeature([User, Profile, Product, Organisation])],
providers: [SeedingService],
controllers: [SeedingController],
})
export class SeedingModule {}
158 changes: 158 additions & 0 deletions src/database/seeding/seeding.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { User } from 'src/entities/user.entity';
import { Profile } from 'src/entities/profile.entity';
import { Product } from 'src/entities/product.entity';
import { Organisation } from 'src/entities/organisation.entity';

@Injectable()
export class SeedingService {
constructor(private readonly dataSource: DataSource) {}

async seedDatabase() {
const userRepository = this.dataSource.getRepository(User);

try {
const existingUsers = await userRepository.count();
if (existingUsers > 0) {
console.log('Database is already populated. Skipping seeding.');
return;
}

const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
const profileRepository = this.dataSource.getRepository(Profile);
const productRepository = this.dataSource.getRepository(Product);
const organisationRepository = this.dataSource.getRepository(Organisation);

const u1 = userRepository.create({
first_name: 'John',
last_name: 'Smith',
email: '[email protected]',
password: 'password',
});
const u2 = userRepository.create({
first_name: 'Jane',
last_name: 'Smith',
email: '[email protected]',
password: 'password',
});

await userRepository.save([u1, u2]);

const savedUsers = await userRepository.find();
if (savedUsers.length !== 2) {
throw new Error('Failed to create all users');
}

const p1 = profileRepository.create({
username: 'johnsmith',
bio: 'bio data',
phone: '1234567890',
avatar_image: 'image.png',
user: savedUsers[0],
});
const p2 = profileRepository.create({
username: 'janesmith',
bio: 'bio data',
phone: '0987654321',
avatar_image: 'image.png',
user: savedUsers[1],
});

await profileRepository.save([p1, p2]);

const savedProfiles = await profileRepository.find();
if (savedProfiles.length !== 2) {
throw new Error('Failed to create all profiles');
}

const pr1 = productRepository.create({
product_name: 'Product 1',
description: 'Description 1',
product_price: 100,
user: savedUsers[0],
});
const pr2 = productRepository.create({
product_name: 'Product 2',
description: 'Description 2',
product_price: 200,
user: savedUsers[1],
});

await productRepository.save([pr1, pr2]);

const savedProducts = await productRepository.find();
if (savedProducts.length !== 2) {
throw new Error('Failed to create all products');
}

const or1 = organisationRepository.create({
org_name: 'Org 1',
description: 'Description 1',
users: savedUsers,
});
const or2 = organisationRepository.create({
org_name: 'Org 2',
description: 'Description 2',
users: [savedUsers[0]],
});

await organisationRepository.save([or1, or2]);

const savedOrganisations = await organisationRepository.find();
if (savedOrganisations.length !== 2) {
throw new Error('Failed to create all organisations');
}

await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
console.error('Seeding failed:', error.message);
} finally {
await queryRunner.release();
}
} catch (error) {
console.error('Error while checking for existing data:', error.message);
}
}

async getUsers(): Promise<User[]> {
try {
return this.dataSource.getRepository(User).find({ relations: ['profile', 'products', 'organisations'] });
} catch (error) {
console.error('Error fetching users:', error.message);
throw error;
}
}

async getProfiles(): Promise<Profile[]> {
try {
return this.dataSource.getRepository(Profile).find({ relations: ['user'] });
} catch (error) {
console.error('Error fetching profiles:', error.message);
throw error;
}
}

async getProducts(): Promise<Product[]> {
try {
return this.dataSource.getRepository(Product).find({ relations: ['user'] });
} catch (error) {
console.error('Error fetching products:', error.message);
throw error;
}
}

async getOrganisations(): Promise<Organisation[]> {
try {
return this.dataSource.getRepository(Organisation).find({ relations: ['users'] });
} catch (error) {
console.error('Error fetching organisations:', error.message);
throw error;
}
}
}
17 changes: 17 additions & 0 deletions src/entities/organisation.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import { User } from './user.entity';

@Entity()
export class Organisation {
@PrimaryGeneratedColumn('uuid')
org_id: string;

@Column({ nullable: false })
org_name: string;

@Column({ nullable: false })
description: string;

@ManyToMany(() => User, user => user.organisations)
users: User[];
}
21 changes: 21 additions & 0 deletions src/entities/product.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { User } from './user.entity';

@Entity()
export class Product {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ nullable: false })
product_name: string;

@Column({ nullable: false })
product_price: number;

@Column({ nullable: false })
description: string;

@ManyToOne(() => User, user => user.products)
@JoinColumn({ name: 'user_id' })
user: User;
}
Loading

0 comments on commit 80358ad

Please sign in to comment.