Skip to content

Commit 0a4db68

Browse files
loading time improvements
1 parent e19247c commit 0a4db68

File tree

20 files changed

+199
-23
lines changed

20 files changed

+199
-23
lines changed

apps/api/ecosystem.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = {
2+
apps: [
3+
{
4+
name: 'mindsmesh-api',
5+
script: 'dist/main.js',
6+
instances: 'max', // Use maximum cores
7+
exec_mode: 'cluster',
8+
autorestart: true,
9+
watch: false,
10+
max_memory_restart: '1G',
11+
env: {
12+
NODE_ENV: 'production',
13+
},
14+
},
15+
],
16+
};

apps/api/src/app.module.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ChatModule } from './chat/chat.module';
1616
import { RoomsModule } from './chat/rooms.module';
1717
import { FirebaseModule } from './firebase/firebase.module';
1818
import firebaseConfig from './config/firebase.config';
19+
import { redisStore } from 'cache-manager-redis-store';
1920

2021
@Module({
2122
imports: [
@@ -31,8 +32,11 @@ import firebaseConfig from './config/firebase.config';
3132
inject: [ConfigService],
3233
}),
3334
CacheModule.register({
34-
ttl: 5, // Cache time-to-live in seconds
35-
max: 100, // Maximum number of items in cache
35+
isGlobal: true,
36+
store: redisStore,
37+
host: process.env.REDIS_HOST || 'localhost',
38+
port: parseInt(process.env.REDIS_PORT) || 6379,
39+
ttl: 60, // 60 seconds default
3640
}),
3741

3842
// ThrottlerModule global configuration

apps/api/src/chat/chat.gateway.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@ import { ChatService } from './chat.service';
1616
import { JwtPayload } from '@/auth/interfaces/jwt-payload.interface';
1717
import { RoomsService } from './rooms.service';
1818

19-
@WebSocketGateway({ cors: { origin: '*' } })
19+
@WebSocketGateway({
20+
cors: {
21+
origin: ['https://mindsmesh.vercel.app', 'https://mindsmesh.netlify.app', 'http://localhost:5173'],
22+
credentials: true
23+
},
24+
transports: ['websocket', 'polling'],
25+
pingInterval: 30000, // Ping every 30 seconds
26+
pingTimeout: 10000, // Consider connection closed if no pong within 10 seconds
27+
cookie: false // Don't use cookies for session tracking
28+
})
2029
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
2130
@WebSocketServer()
2231
server: Server;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { IsOptional, IsInt, Min, Max } from 'class-validator';
2+
import { Type } from 'class-transformer';
3+
import { ApiProperty } from '@nestjs/swagger';
4+
5+
export class PaginationDto {
6+
@ApiProperty({ required: false, default: 1 })
7+
@IsOptional()
8+
@Type(() => Number)
9+
@IsInt()
10+
@Min(1)
11+
page?: number = 1;
12+
13+
@ApiProperty({ required: false, default: 10 })
14+
@IsOptional()
15+
@Type(() => Number)
16+
@IsInt()
17+
@Min(1)
18+
@Max(100)
19+
limit?: number = 10;
20+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2+
import { Observable } from 'rxjs';
3+
import { map } from 'rxjs/operators';
4+
import { createHash } from 'crypto';
5+
6+
@Injectable()
7+
export class EtagInterceptor implements NestInterceptor {
8+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
9+
return next.handle().pipe(
10+
map(data => {
11+
const response = context.switchToHttp().getResponse();
12+
const request = context.switchToHttp().getRequest();
13+
14+
// Generate ETag from response data
15+
const etag = createHash('md5').update(JSON.stringify(data)).digest('hex');
16+
response.setHeader('ETag', etag);
17+
18+
// Check if client has matching ETag
19+
const ifNoneMatch = request.headers['if-none-match'];
20+
if (ifNoneMatch === etag) {
21+
response.status(304).send();
22+
return null;
23+
}
24+
25+
return data;
26+
}),
27+
);
28+
}
29+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
2+
import { Observable, throwError, TimeoutError } from 'rxjs';
3+
import { catchError, timeout } from 'rxjs/operators';
4+
5+
@Injectable()
6+
export class TimeoutInterceptor implements NestInterceptor {
7+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
8+
return next.handle().pipe(
9+
timeout(10000), // 10 seconds timeout
10+
catchError(err => {
11+
if (err instanceof TimeoutError) {
12+
return throwError(() => new RequestTimeoutException('Request timeout'));
13+
}
14+
return throwError(() => err);
15+
}),
16+
);
17+
}
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2+
import { Observable } from 'rxjs';
3+
import { map } from 'rxjs/operators';
4+
5+
export interface Response<T> {
6+
data: T;
7+
timestamp: number;
8+
}
9+
10+
@Injectable()
11+
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
12+
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
13+
return next.handle().pipe(
14+
map(data => ({
15+
data,
16+
timestamp: Date.now(),
17+
})),
18+
);
19+
}
20+
}

apps/api/src/config/typeorm.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@ const config = {
1010
entities: ["dist/**/*.entity{.ts,.js}"],
1111
migrations: ["dist/migrations/*{.ts,.js}"],
1212
autoLoadEntities: true,
13-
synchronize: true,
13+
synchronize: false,
1414
ssl: true,
1515
extra: {
1616
ssl: {
1717
rejectUnauthorized: false,
1818
},
19-
connectionTimeoutMillis: 30000,
19+
connectionTimeoutMillis: 10000,
2020
idleTimeoutMillis: 30000,
21-
max: 10,
21+
max: 20,
22+
min: 5,
23+
maxUses: 7500,
2224
},
25+
logging: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
2326
} as DataSourceOptions;
2427

2528
export default registerAs('typeorm', () => config);

apps/api/src/main.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { AppModule } from './app.module';
33
import { ValidationPipe, Logger } from '@nestjs/common';
44
import * as bodyParser from 'body-parser';
55
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
6+
import * as compression from 'compression';
7+
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
8+
import { LoggingInterceptor } from './common/interceptors/logging.interceptor';
69

710
async function bootstrap() {
811
const logger = new Logger('Bootstrap');
@@ -30,6 +33,10 @@ async function bootstrap() {
3033
allowedHeaders: 'Content-Type, Authorization',
3134
credentials: true,
3235
});
36+
37+
// Add compression
38+
app.use(compression());
39+
3340
// Swagger configuration
3441
const config = new DocumentBuilder()
3542
.setTitle('API Documentation')
@@ -40,6 +47,12 @@ async function bootstrap() {
4047
const document = SwaggerModule.createDocument(app, config);
4148
SwaggerModule.setup('api-docs', app, document);
4249

50+
// Add global interceptors
51+
app.useGlobalInterceptors(
52+
new TransformInterceptor(),
53+
new LoggingInterceptor(),
54+
);
55+
4356
const PORT = process.env.PORT || 3000;
4457

4558
// Start listening on the specified PORT and bind to all network interfaces

apps/api/src/users/entities/user.entity.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { ApiProperty, ApiHideProperty } from '@nestjs/swagger';
2-
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
2+
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, Index } from 'typeorm';
33
import { Skill } from '../../skills/entities/skill.entity';
44
import { IsOptional } from 'class-validator';
55
import { ChatMessage } from '@/chat/entities/chat-message.entity';
66
import { UserRole } from '../enums/user-role.enum';
77
import { Room } from '@/chat/entities/room.entity';
88

99
@Entity()
10+
@Index(['email'])
11+
@Index(['role'])
1012
export class User {
1113
@ApiProperty({
1214
example: 'uuid-1234',

0 commit comments

Comments
 (0)