feat: implement master data and cohort management modules with CRUD operations and database seeding

This commit is contained in:
azeeee05
2026-07-07 17:23:45 +05:30
parent 82ea313f34
commit 8021937518
19 changed files with 562 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from '../src/app.module';
import { MasterDataService } from '../src/modules/master-data/master-data.service';
import { Logger } from '@nestjs/common';
async function bootstrap() {
const logger = new Logger('SeedMasterData');
logger.log('Starting standalone seed script...');
// Initialize the NestJS application context (this sets up TypeORM and ConfigModule)
const app = await NestFactory.createApplicationContext(AppModule);
// Get our service
const masterDataService = app.get(MasterDataService);
// Run the seed logic
await masterDataService.seedData();
logger.log('Seeding complete. Exiting...');
// Close DB connections
await app.close();
process.exit(0);
}
bootstrap().catch((err) => {
console.error('Failed to run seed script:', err);
process.exit(1);
});
+26
View File
@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get<string>('DB_HOST'),
port: Number(configService.get<string>('DB_PORT') ?? 5432),
username: configService.get<string>('DB_USER'),
password: configService.get<string>('DB_PASSWORD'),
database: configService.get<string>('DB_NAME'),
synchronize: configService.get<string>('DB_SYNCHRONIZE') === 'true',
autoLoadEntities: true,
uuidExtension: 'pgcrypto',
ssl: configService.get<string>('DB_SSL') === 'true' ? { rejectUnauthorized: false } : false,
logging: false,
}),
}),
],
})
export class DatabaseModule {}
+12
View File
@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { CohortService } from './cohort.service';
@Controller('cohorts')
export class CohortController {
constructor(private readonly cohortService: CohortService) {}
@Get()
async findAll() {
return this.cohortService.findAll();
}
}
+16
View File
@@ -0,0 +1,16 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('cohorts')
export class Cohort {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
+13
View File
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CohortController } from './cohort.controller';
import { CohortService } from './cohort.service';
import { Cohort } from './cohort.entity';
@Module({
imports: [TypeOrmModule.forFeature([Cohort])],
controllers: [CohortController],
providers: [CohortService],
exports: [CohortService],
})
export class CohortModule {}
+16
View File
@@ -0,0 +1,16 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cohort } from './cohort.entity';
@Injectable()
export class CohortService {
constructor(
@InjectRepository(Cohort)
private readonly cohortRepository: Repository<Cohort>,
) {}
async findAll(): Promise<Cohort[]> {
return this.cohortRepository.find();
}
}
@@ -0,0 +1,19 @@
import { IsString, IsNotEmpty, IsBoolean, IsOptional, IsNumber } from 'class-validator';
export class CreateMasterDataDto {
@IsString()
@IsNotEmpty()
label: string;
@IsString()
@IsNotEmpty()
value: string;
@IsBoolean()
@IsOptional()
isActive?: boolean;
@IsNumber()
@IsOptional()
sortOrder?: number;
}
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateMasterDataDto } from './create-master-data.dto';
export class UpdateMasterDataDto extends PartialType(CreateMasterDataDto) {}
@@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('ancillary_purchases')
export class AncillaryPurchase {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
label: string;
@Column()
value: string;
@Column({ default: true })
isActive: boolean;
@Column({ default: 0 })
sortOrder: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('cabin_classes')
export class CabinClass {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
label: string;
@Column()
value: string;
@Column({ default: true })
isActive: boolean;
@Column({ default: 0 })
sortOrder: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('customer_values')
export class CustomerValue {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
label: string;
@Column()
value: string;
@Column({ default: true })
isActive: boolean;
@Column({ default: 0 })
sortOrder: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('membership_tiers')
export class MembershipTier {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
label: string;
@Column()
value: string;
@Column({ default: true })
isActive: boolean;
@Column({ default: 0 })
sortOrder: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('passenger_types')
export class PassengerType {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
label: string;
@Column()
value: string;
@Column({ default: true })
isActive: boolean;
@Column({ default: 0 })
sortOrder: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('regions')
export class Region {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
label: string;
@Column()
value: string;
@Column({ default: true })
isActive: boolean;
@Column({ default: 0 })
sortOrder: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@@ -0,0 +1,25 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('trip_purposes')
export class TripPurpose {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
label: string;
@Column()
value: string;
@Column({ default: true })
isActive: boolean;
@Column({ default: 0 })
sortOrder: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@@ -0,0 +1,52 @@
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { MasterDataService } from './master-data.service';
import { CreateMasterDataDto } from './dto/create-master-data.dto';
import { UpdateMasterDataDto } from './dto/update-master-data.dto';
@Controller('master-data')
export class MasterDataController {
constructor(private readonly masterDataService: MasterDataService) {}
// Example: POST /master-data/REGION
@Post(':category')
async create(
@Param('category') category: string,
@Body() createDto: CreateMasterDataDto,
) {
return this.masterDataService.create(category, createDto);
}
// Example: GET /master-data/REGION
@Get(':category')
async findAll(@Param('category') category: string) {
return this.masterDataService.findAll(category);
}
// Example: GET /master-data/REGION/uuid
@Get(':category/:id')
async findOne(
@Param('category') category: string,
@Param('id') id: string,
) {
return this.masterDataService.findOne(category, id);
}
// Example: PUT /master-data/REGION/uuid
@Put(':category/:id')
async update(
@Param('category') category: string,
@Param('id') id: string,
@Body() updateDto: UpdateMasterDataDto,
) {
return this.masterDataService.update(category, id, updateDto);
}
// Example: DELETE /master-data/REGION/uuid
@Delete(':category/:id')
async remove(
@Param('category') category: string,
@Param('id') id: string,
) {
return this.masterDataService.remove(category, id);
}
}
@@ -0,0 +1,2 @@
// This file is obsolete. The entities have been separated into the `entities` folder.
// You can delete this file.
@@ -0,0 +1,30 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MasterDataController } from './master-data.controller';
import { MasterDataService } from './master-data.service';
import { MembershipTier } from './entities/membership-tier.entity';
import { CustomerValue } from './entities/customer-value.entity';
import { Region } from './entities/region.entity';
import { TripPurpose } from './entities/trip-purpose.entity';
import { CabinClass } from './entities/cabin-class.entity';
import { PassengerType } from './entities/passenger-type.entity';
import { AncillaryPurchase } from './entities/ancillary-purchase.entity';
@Module({
imports: [
TypeOrmModule.forFeature([
MembershipTier,
CustomerValue,
Region,
TripPurpose,
CabinClass,
PassengerType,
AncillaryPurchase,
]),
],
controllers: [MasterDataController],
providers: [MasterDataService],
exports: [MasterDataService],
})
export class MasterDataModule {}
@@ -0,0 +1,168 @@
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { MembershipTier } from './entities/membership-tier.entity';
import { CustomerValue } from './entities/customer-value.entity';
import { Region } from './entities/region.entity';
import { TripPurpose } from './entities/trip-purpose.entity';
import { CabinClass } from './entities/cabin-class.entity';
import { PassengerType } from './entities/passenger-type.entity';
import { AncillaryPurchase } from './entities/ancillary-purchase.entity';
@Injectable()
export class MasterDataService {
private readonly logger = new Logger(MasterDataService.name);
constructor(
@InjectRepository(MembershipTier) private membershipTierRepo: Repository<MembershipTier>,
@InjectRepository(CustomerValue) private customerValueRepo: Repository<CustomerValue>,
@InjectRepository(Region) private regionRepo: Repository<Region>,
@InjectRepository(TripPurpose) private tripPurposeRepo: Repository<TripPurpose>,
@InjectRepository(CabinClass) private cabinClassRepo: Repository<CabinClass>,
@InjectRepository(PassengerType) private passengerTypeRepo: Repository<PassengerType>,
@InjectRepository(AncillaryPurchase) private ancillaryPurchaseRepo: Repository<AncillaryPurchase>,
) {}
// Helper to get the correct repository based on category
private getRepositoryByCategory(category: string): Repository<any> {
switch (category?.toUpperCase()) {
case 'MEMBERSHIP_TIER': return this.membershipTierRepo;
case 'CUSTOMER_VALUE': return this.customerValueRepo;
case 'REGION': return this.regionRepo;
case 'TRIP_PURPOSE': return this.tripPurposeRepo;
case 'CABIN_CLASS': return this.cabinClassRepo;
case 'PASSENGER_TYPE': return this.passengerTypeRepo;
case 'ANCILLARY_PURCHASE': return this.ancillaryPurchaseRepo;
default:
throw new BadRequestException(`Invalid category: ${category}`);
}
}
async findAll(category: string): Promise<any[]> {
if (!category) {
throw new BadRequestException('Category is required');
}
const repo = this.getRepositoryByCategory(category);
return repo.find({
where: { isActive: true },
order: { sortOrder: 'ASC' },
});
}
async findOne(category: string, id: string): Promise<any> {
const repo = this.getRepositoryByCategory(category);
const item = await repo.findOne({ where: { id } });
if (!item) {
throw new BadRequestException(`${category} with id ${id} not found`);
}
return item;
}
async create(category: string, data: any): Promise<any> {
const repo = this.getRepositoryByCategory(category);
const newItem = repo.create(data);
return repo.save(newItem);
}
async update(category: string, id: string, data: any): Promise<any> {
const repo = this.getRepositoryByCategory(category);
const item = await this.findOne(category, id);
Object.assign(item, data);
return repo.save(item);
}
async remove(category: string, id: string): Promise<void> {
const repo = this.getRepositoryByCategory(category);
const item = await this.findOne(category, id);
await repo.remove(item);
}
public async seedData() {
this.logger.log('Checking if separate master data tables need seeding...');
// MEMBERSHIP_TIER
if (await this.membershipTierRepo.count() === 0) {
const data = [
{ label: 'Platinum', value: 'platinum', sortOrder: 1 },
{ label: 'Gold', value: 'gold', sortOrder: 2 },
{ label: 'Silver', value: 'silver', sortOrder: 3 },
{ label: 'Bronze', value: 'bronze', sortOrder: 4 },
{ label: 'Basic', value: 'basic', sortOrder: 5 },
{ label: 'Non-Member', value: 'non-member', sortOrder: 6 },
];
await this.membershipTierRepo.save(this.membershipTierRepo.create(data));
}
// CUSTOMER_VALUE
if (await this.customerValueRepo.count() === 0) {
const data = [
{ label: 'High Value', value: 'high', sortOrder: 1 },
{ label: 'Medium Value', value: 'medium', sortOrder: 2 },
{ label: 'Low Value', value: 'low', sortOrder: 3 },
];
await this.customerValueRepo.save(this.customerValueRepo.create(data));
}
// REGION
if (await this.regionRepo.count() === 0) {
const data = [
{ label: 'Global', value: 'global', sortOrder: 1 },
{ label: 'Americas', value: 'americas', sortOrder: 2 },
{ label: 'Europe', value: 'europe', sortOrder: 3 },
{ label: 'Middle East', value: 'middle-east', sortOrder: 4 },
{ label: 'Asia Pacific', value: 'asia-pacific', sortOrder: 5 },
{ label: 'Africa', value: 'africa', sortOrder: 6 },
];
await this.regionRepo.save(this.regionRepo.create(data));
}
// TRIP_PURPOSE
if (await this.tripPurposeRepo.count() === 0) {
const data = [
{ label: 'Business', value: 'business', sortOrder: 1 },
{ label: 'Leisure', value: 'leisure', sortOrder: 2 },
{ label: 'Corporate', value: 'corporate', sortOrder: 3 },
{ label: 'Government', value: 'government', sortOrder: 4 },
];
await this.tripPurposeRepo.save(this.tripPurposeRepo.create(data));
}
// CABIN_CLASS
if (await this.cabinClassRepo.count() === 0) {
const data = [
{ label: 'First Class', value: 'first-class', sortOrder: 1 },
{ label: 'Business Class', value: 'business-class', sortOrder: 2 },
{ label: 'Premium Economy', value: 'premium-economy', sortOrder: 3 },
{ label: 'Economy', value: 'economy', sortOrder: 4 },
];
await this.cabinClassRepo.save(this.cabinClassRepo.create(data));
}
// PASSENGER_TYPE
if (await this.passengerTypeRepo.count() === 0) {
const data = [
{ label: 'Adult', value: 'adult', sortOrder: 1 },
{ label: 'Child', value: 'child', sortOrder: 2 },
{ label: 'Infant', value: 'infant', sortOrder: 3 },
{ label: 'Senior Citizen', value: 'senior', sortOrder: 4 },
];
await this.passengerTypeRepo.save(this.passengerTypeRepo.create(data));
}
// ANCILLARY_PURCHASE
if (await this.ancillaryPurchaseRepo.count() === 0) {
const data = [
{ label: 'Extra Baggage', value: 'extra-baggage', sortOrder: 1 },
{ label: 'Seat Selection', value: 'seat-selection', sortOrder: 2 },
{ label: 'In-Flight Meal', value: 'in-flight-meal', sortOrder: 3 },
{ label: 'Lounge Access', value: 'lounge-access', sortOrder: 4 },
{ label: 'Priority Boarding', value: 'priority-boarding', sortOrder: 5 },
];
await this.ancillaryPurchaseRepo.save(this.ancillaryPurchaseRepo.create(data));
}
this.logger.log('All 7 master data tables seeded successfully!');
}
}