feat: implement master data and cohort management modules with CRUD operations and database seeding
This commit is contained in:
@@ -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);
|
||||
});
|
||||
@@ -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 {}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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!');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user