Skip to content

Commit fd2dd59

Browse files
authored
Merge pull request #406 from ustaxs/feat/filtering
Title: Feat: Implement Advanced Pagination and Filtering System
2 parents 7f7a3f5 + 739b8c4 commit fd2dd59

26 files changed

Lines changed: 581 additions & 82 deletions

src/app.module.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { BullModule } from '@nestjs/bullmq';
12
import { Module } from '@nestjs/common';
23
import { ConfigModule, ConfigService } from '@nestjs/config';
34
import { ScheduleModule } from '@nestjs/schedule';
@@ -6,13 +7,28 @@ import { CacheWarmingModule } from './cache-warming/cache-warming.module';
67
import { CacheJob } from './cache-warming/entities/cache-job.entity';
78
import { Metric } from './cache-warming/entities/metric.entity';
89
import { PreloadData } from './cache-warming/entities/preload-data.entity';
10+
import { DeadLetterEvent } from './events/dead-letter-event.entity';
11+
import { Event } from './events/event.entity';
12+
import { EventsModule } from './events/events.module';
13+
import { JobsModule } from './jobs/jobs.module';
14+
import { Job } from './jobs/job.entity';
915

1016
@Module({
1117
imports: [
1218
ConfigModule.forRoot({
1319
isGlobal: true,
1420
envFilePath: ['.env.local', '.env'],
1521
}),
22+
BullModule.forRootAsync({
23+
imports: [ConfigModule],
24+
useFactory: (configService: ConfigService) => ({
25+
connection: {
26+
host: configService.get<string>('REDIS_HOST', 'localhost'),
27+
port: configService.get<number>('REDIS_PORT', 6379),
28+
},
29+
}),
30+
inject: [ConfigService],
31+
}),
1632
TypeOrmModule.forRootAsync({
1733
imports: [ConfigModule],
1834
useFactory: (configService: ConfigService) => ({
@@ -22,14 +38,16 @@ import { PreloadData } from './cache-warming/entities/preload-data.entity';
2238
username: configService.get<string>('DB_USER', 'postgres'),
2339
password: configService.get<string>('DB_PASSWORD', 'password'),
2440
database: configService.get<string>('DB_NAME', 'cache_warming_db'),
25-
entities: [CacheJob, PreloadData, Metric],
41+
entities: [CacheJob, PreloadData, Metric, Event, DeadLetterEvent, Job],
2642
synchronize: configService.get<string>('NODE_ENV') !== 'production',
2743
logging: configService.get<string>('NODE_ENV') === 'development',
2844
}),
2945
inject: [ConfigService],
3046
}),
3147
ScheduleModule.forRoot(),
3248
CacheWarmingModule,
49+
EventsModule,
50+
JobsModule,
3351
],
3452
})
35-
export class AppModule {}
53+
export class AppModule {}

src/common/pagination/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './page.dto';
2+
export * from './page-meta.dto';
3+
export * from './page-options.dto';
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { PageOptionsDto } from './page-options.dto';
2+
3+
export class PageMetaDto {
4+
readonly page: number;
5+
readonly limit: number;
6+
readonly itemCount: number;
7+
readonly pageCount: number;
8+
readonly hasPreviousPage: boolean;
9+
readonly hasNextPage: boolean;
10+
readonly nextCursor?: string;
11+
12+
constructor({
13+
pageOptionsDto,
14+
itemCount,
15+
nextCursor,
16+
}: {
17+
pageOptionsDto: PageOptionsDto;
18+
itemCount: number;
19+
nextCursor?: string;
20+
}) {
21+
this.page = pageOptionsDto.page;
22+
this.limit = pageOptionsDto.limit;
23+
this.itemCount = itemCount;
24+
this.pageCount = Math.ceil(this.itemCount / this.limit);
25+
this.hasPreviousPage = this.page > 1;
26+
this.hasNextPage = this.page < this.pageCount;
27+
this.nextCursor = nextCursor;
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
2+
import { Type } from 'class-transformer';
3+
4+
export class PageOptionsDto {
5+
@Type(() => Number)
6+
@IsInt()
7+
@Min(1)
8+
@IsOptional()
9+
readonly page?: number = 1;
10+
11+
@Type(() => Number)
12+
@IsInt()
13+
@Min(1)
14+
@Max(100)
15+
@IsOptional()
16+
readonly limit?: number = 10;
17+
18+
@IsString()
19+
@IsOptional()
20+
readonly sort?: string;
21+
22+
@IsString()
23+
@IsOptional()
24+
readonly filter?: string;
25+
26+
@IsString()
27+
@IsOptional()
28+
readonly cursor?: string;
29+
}

src/common/pagination/page.dto.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { IsArray } from 'class-validator';
2+
import { PageMetaDto } from './page-meta.dto';
3+
4+
export class PageDto<T> {
5+
@IsArray()
6+
readonly data: T[];
7+
8+
readonly meta: PageMetaDto;
9+
10+
constructor(data: T[], meta: PageMetaDto) {
11+
this.data = data;
12+
this.meta = meta;
13+
}
14+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {
2+
Column,
3+
CreateDateColumn,
4+
Entity,
5+
PrimaryGeneratedColumn,
6+
} from 'typeorm';
7+
8+
@Entity('dead_letter_events')
9+
export class DeadLetterEvent {
10+
@PrimaryGeneratedColumn('uuid')
11+
id: string;
12+
13+
@Column()
14+
topic: string;
15+
16+
@Column({ type: 'jsonb' })
17+
payload: any;
18+
19+
@Column({ type: 'text' })
20+
error: string;
21+
22+
@CreateDateColumn({ type: 'timestamp' })
23+
failedAt: Date;
24+
}

src/events/dead-letter.service.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
import { Repository } from 'typeorm';
4+
import { DeadLetterEvent } from './dead-letter-event.entity';
5+
6+
@Injectable()
7+
export class DeadLetterService {
8+
constructor(
9+
@InjectRepository(DeadLetterEvent)
10+
private readonly deadLetterEventRepository: Repository<DeadLetterEvent>,
11+
) {}
12+
13+
async replay(): Promise<void> {
14+
// Replay logic will go here
15+
}
16+
}

src/events/event.entity.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {
2+
Column,
3+
CreateDateColumn,
4+
Entity,
5+
PrimaryGeneratedColumn,
6+
} from 'typeorm';
7+
8+
@Entity('events')
9+
export class Event {
10+
@PrimaryGeneratedColumn('uuid')
11+
id: string;
12+
13+
@Column()
14+
topic: string;
15+
16+
@Column({ type: 'jsonb' })
17+
payload: any;
18+
19+
@CreateDateColumn({ type: 'timestamp' })
20+
timestamp: Date;
21+
}

src/events/events.emitter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { EventEmitter } from 'events';
2+
3+
export const eventsEmitter = new EventEmitter();

src/events/events.module.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { DeadLetterEvent } from './dead-letter-event.entity';
4+
import { DeadLetterService } from './dead-letter.service';
5+
import { Event } from './event.entity';
6+
import { EventsService } from './events.service';
7+
8+
@Module({
9+
imports: [TypeOrmModule.forFeature([Event, DeadLetterEvent])],
10+
providers: [EventsService, DeadLetterService],
11+
exports: [EventsService, DeadLetterService],
12+
})
13+
export class EventsModule {}

0 commit comments

Comments
 (0)