Skip to content

Commit ae4083f

Browse files
committed
refactor: improve user config generation and module structure
- Refactored XRay config generation to optimize user and inbound handling - Added support for excluding specific inbounds when fetching users - Simplified XRay config module and service initialization - Enhanced users repository query to support inbound exclusion - Improved memory management in config generation methods
1 parent cfe18e7 commit ae4083f

File tree

10 files changed

+128
-62
lines changed

10 files changed

+128
-62
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"migrate:generate": "prisma generate",
3636
"migrate:deploy": "PRISMA_HIDE_UPDATE_MESSAGE=true prisma migrate deploy",
3737
"migrate:seed": "prisma db seed",
38+
"migrate:seed:dev": "npm run build:seed && prisma db seed",
3839
"docs:serve": "compodoc -p tsconfig.json -s",
3940
"generate:openapi": "nest start --config nest-cli.gen-doc.json"
4041
},

src/common/helpers/xray-config/xray-config.validator.ts

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -207,56 +207,72 @@ export class XRayConfig {
207207
}
208208

209209
public includeUsers(users: UserForConfigEntity[]): IXrayConfig {
210-
const config = structuredClone(this.config);
210+
const config = JSON.parse(JSON.stringify(this.config)) as IXrayConfig;
211211

212212
const inboundMap = new Map(config.inbounds.map((inbound) => [inbound.tag, inbound]));
213213

214+
const usersByTag = new Map<string, UserForConfigEntity[]>();
214215
for (const user of users) {
215-
const inbound = inboundMap.get(user.tag);
216-
if (!inbound) {
217-
continue;
216+
if (!usersByTag.has(user.tag)) {
217+
usersByTag.set(user.tag, []);
218218
}
219+
usersByTag.get(user.tag)!.push(user);
220+
}
221+
222+
for (const [tag, tagUsers] of usersByTag) {
223+
const inbound = inboundMap.get(tag);
224+
if (!inbound) continue;
219225

220226
inbound.settings ??= {} as InboundSettings;
221-
// inbound.settings.clients ??= [];
222227

223-
switch (inbound.protocol) {
224-
case 'trojan':
225-
(inbound.settings as TrojanSettings).clients ??= [];
228+
this.addUsersToInbound(inbound, tagUsers);
229+
}
230+
231+
return config;
232+
}
233+
234+
public prepareConfigForNode(users: UserForConfigEntity[]): IXrayConfig {
235+
const configWithUsers = this.includeUsers(users);
236+
return this.processCertificates(configWithUsers);
237+
}
238+
239+
public getSortedConfig(): IXrayConfig {
240+
return this.sortObjectByKeys<IXrayConfig>(this.config);
241+
}
242+
243+
private addUsersToInbound(inbound: Inbound, users: UserForConfigEntity[]): void {
244+
switch (inbound.protocol) {
245+
case 'trojan':
246+
(inbound.settings as TrojanSettings).clients ??= [];
247+
for (const user of users) {
226248
(inbound.settings as TrojanSettings).clients.push({
227249
password: user.trojanPassword,
228250
email: `${user.username}`,
229251
});
230-
break;
231-
case 'vless':
232-
(inbound.settings as VLessSettings).clients ??= [];
252+
}
253+
break;
254+
case 'vless':
255+
(inbound.settings as VLessSettings).clients ??= [];
256+
for (const user of users) {
233257
(inbound.settings as VLessSettings).clients.push({
234258
id: user.vlessUuid,
235259
email: `${user.username}`,
236260
flow: getVlessFlow(inbound),
237261
});
238-
break;
239-
case 'shadowsocks':
262+
}
263+
break;
264+
case 'shadowsocks':
265+
(inbound.settings as ShadowsocksSettings).clients ??= [];
266+
for (const user of users) {
240267
(inbound.settings as ShadowsocksSettings).clients.push({
241268
password: user.ssPassword,
242269
method: 'chacha20-ietf-poly1305',
243270
email: user.username,
244271
});
245-
break;
246-
default:
247-
throw new Error(`Protocol ${inbound.protocol} is not supported.`);
248-
}
272+
}
273+
break;
274+
default:
275+
throw new Error(`Protocol ${inbound.protocol} is not supported.`);
249276
}
250-
251-
return config;
252-
}
253-
254-
public prepareConfigForNode(users: UserForConfigEntity[]): IXrayConfig {
255-
const configWithUsers = this.includeUsers(users);
256-
return this.processCertificates(configWithUsers);
257-
}
258-
259-
public getSortedConfig(): IXrayConfig {
260-
return this.sortObjectByKeys<IXrayConfig>(this.config);
261277
}
262278
}

src/modules/users/builders/users-with-inbound-tag/users-with-inbound-tag.builder.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Prisma } from '@prisma/client';
22

33
import { USERS_STATUS } from '@libs/contracts/constants';
44

5+
import { InboundsEntity } from '@modules/inbounds/entities';
6+
57
export class UsersWithInboundTagBuilder {
68
public query: Prisma.Sql;
79

@@ -30,3 +32,40 @@ export class UsersWithInboundTagBuilder {
3032
return Prisma.raw(query);
3133
}
3234
}
35+
36+
export class UsersWithInboundTagAndExcludedInboundsBuilder {
37+
public query: Prisma.Sql;
38+
private excludedInbounds: InboundsEntity[];
39+
40+
constructor(excludedInbounds: InboundsEntity[]) {
41+
this.excludedInbounds = excludedInbounds;
42+
this.query = this.getQuery();
43+
return this;
44+
}
45+
46+
public getQuery(): Prisma.Sql {
47+
const excludedUuidsCondition =
48+
this.excludedInbounds.length > 0
49+
? `AND i.uuid NOT IN (${this.excludedInbounds.map((inbound) => `'${inbound.uuid}'`).join(',')})`
50+
: '';
51+
52+
const query = `
53+
SELECT
54+
u.subscription_uuid as "subscriptionUuid",
55+
u.username,
56+
u.trojan_password as "trojanPassword",
57+
u.vless_uuid as "vlessUuid",
58+
u.ss_password as "ssPassword",
59+
i.tag
60+
FROM users u
61+
INNER JOIN active_user_inbounds aui
62+
ON aui.user_uuid = u.uuid
63+
INNER JOIN inbounds i
64+
ON i.uuid = aui.inbound_uuid
65+
WHERE u.status = '${USERS_STATUS.ACTIVE}'
66+
${excludedUuidsCondition}
67+
`;
68+
69+
return Prisma.raw(query);
70+
}
71+
}

src/modules/users/queries/get-users-for-config/get-users-for-config.handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ export class GetUsersForConfigHandler
1515
private readonly logger = new Logger(GetUsersForConfigHandler.name);
1616
constructor(private readonly usersRepository: UsersRepository) {}
1717

18-
async execute(): Promise<ICommandResponse<UserForConfigEntity[]>> {
18+
async execute(query: GetUsersForConfigQuery): Promise<ICommandResponse<UserForConfigEntity[]>> {
1919
let users: UserForConfigEntity[] | null = null;
2020

2121
try {
22-
users = await this.usersRepository.getUsersForConfig();
22+
users = await this.usersRepository.getUsersForConfig(query.excludedInbounds);
2323

2424
return {
2525
isOk: true,
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { InboundsEntity } from '@modules/inbounds/entities';
2+
13
export class GetUsersForConfigQuery {
2-
constructor() {}
4+
constructor(public readonly excludedInbounds: InboundsEntity[]) {}
35
}

src/modules/users/repositories/users.repository.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import { Injectable } from '@nestjs/common';
1010
import { ICrud } from '@common/types/crud-port';
1111
import { GetAllUsersV2Command } from '@libs/contracts/commands';
1212

13+
import { InboundsEntity } from '@modules/inbounds/entities';
14+
1315
import {
1416
BatchResetLimitedUsersUsageBuilder,
1517
BatchResetUsersUsageBuilder,
1618
BulkDeleteByStatusBuilder,
1719
BulkUpdateUserUsedTrafficBuilder,
18-
UsersWithInboundTagBuilder,
20+
UsersWithInboundTagAndExcludedInboundsBuilder,
1921
} from '../builders';
2022
import {
2123
UserEntity,
@@ -271,8 +273,10 @@ export class UsersRepository implements ICrud<UserEntity> {
271273
return result.map((value) => new UserWithActiveInboundsEntity(value));
272274
}
273275

274-
public async getUsersForConfig(): Promise<UserForConfigEntity[]> {
275-
const { query } = new UsersWithInboundTagBuilder();
276+
public async getUsersForConfig(
277+
excludedInbounds: InboundsEntity[],
278+
): Promise<UserForConfigEntity[]> {
279+
const { query } = new UsersWithInboundTagAndExcludedInboundsBuilder(excludedInbounds);
276280
return await this.prisma.tx.$queryRaw<UserForConfigEntity[]>(query);
277281
}
278282

src/modules/xray-config/queries/get-prepared-config-with-users/get-prepared-config-with-users.handler.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,46 +23,48 @@ export class GetPreparedConfigWithUsersHandler
2323
) {}
2424

2525
async execute(query: GetPreparedConfigWithUsersQuery): Promise<ICommandResponse<IXrayConfig>> {
26+
let config: ICommandResponse<IXrayConfig> | null = null;
27+
let users: ICommandResponse<UserForConfigEntity[]> | null = null;
28+
2629
try {
2730
const { excludedInbounds } = query;
28-
const excludedTags = new Set(excludedInbounds.map((inbound) => inbound.tag));
2931

30-
const users = await this.getUsersForConfig();
32+
users = await this.getUsersForConfig({
33+
excludedInbounds,
34+
});
3135

3236
if (!users.isOk || !users.response) {
3337
throw new Error('Failed to get users for config');
3438
}
3539

36-
const config = await this.xrayService.getConfigWithUsers(users.response);
40+
config = await this.xrayService.getConfigWithUsers(users.response);
3741

3842
if (!config.response) {
3943
throw new Error('Config response is empty');
4044
}
4145

42-
const filteredConfig: IXrayConfig = {
43-
...config.response,
44-
inbounds: config.response.inbounds.filter(
45-
(inbound) => !excludedTags.has(inbound.tag),
46-
),
47-
};
48-
4946
return {
5047
isOk: true,
51-
response: filteredConfig,
48+
response: config.response,
5249
};
5350
} catch (error) {
5451
this.logger.error(error);
5552
return {
5653
isOk: false,
5754
...ERRORS.INTERNAL_SERVER_ERROR,
5855
};
56+
} finally {
57+
config = null;
58+
users = null;
5959
}
6060
}
6161

62-
private getUsersForConfig(): Promise<ICommandResponse<UserForConfigEntity[]>> {
62+
private getUsersForConfig(
63+
dto: GetPreparedConfigWithUsersQuery,
64+
): Promise<ICommandResponse<UserForConfigEntity[]>> {
6365
return this.queryBus.execute<
6466
GetUsersForConfigQuery,
6567
ICommandResponse<UserForConfigEntity[]>
66-
>(new GetUsersForConfigQuery());
68+
>(new GetUsersForConfigQuery(dto.excludedInbounds));
6769
}
6870
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { GetPreparedConfigWithUsersHandler } from './get-prepared-config-with-users';
22
import { GetValidatedConfigHandler } from './get-validated-config';
33

4-
export const QUERIES = [GetPreparedConfigWithUsersHandler, GetValidatedConfigHandler];
4+
export const QUERIES = [GetValidatedConfigHandler, GetPreparedConfigWithUsersHandler];
Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
1-
import { Module, OnApplicationBootstrap } from '@nestjs/common';
21
import { CqrsModule } from '@nestjs/cqrs';
2+
import { Module } from '@nestjs/common';
33

44
import { XrayConfigRepository } from './repositories/xray-config.repository';
55
import { XrayConfigController } from './xray-config.controller';
66
import { XrayConfigConverter } from './xray-config.converter';
77
import { XrayConfigService } from './xray-config.service';
88
import { QUERIES } from './queries';
9+
910
@Module({
1011
imports: [CqrsModule],
1112
controllers: [XrayConfigController],
12-
providers: [XrayConfigRepository, XrayConfigConverter, XrayConfigService, ...QUERIES],
13+
providers: [XrayConfigService, XrayConfigRepository, XrayConfigConverter, ...QUERIES],
14+
exports: [],
1315
})
14-
export class XrayConfigModule implements OnApplicationBootstrap {
15-
constructor(private readonly xrayConfigService: XrayConfigService) {}
16-
17-
async onApplicationBootstrap() {
18-
await this.xrayConfigService.syncInbounds();
19-
}
20-
}
21-
// export class XrayConfigModule {}
16+
export class XrayConfigModule {}

src/modules/xray-config/xray-config.service.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ERRORS } from '@contract/constants';
22

3+
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
34
import { CommandBus, EventBus, QueryBus } from '@nestjs/cqrs';
4-
import { Injectable, Logger } from '@nestjs/common';
55

66
import { ICommandResponse } from '@common/types/command-response.type';
77
import { IXrayConfig } from '@common/helpers/xray-config/interfaces';
@@ -21,16 +21,20 @@ import { UpdateConfigRequestDto } from './dtos/update-config.dto';
2121
import { XrayConfigEntity } from './entities/xray-config.entity';
2222

2323
@Injectable()
24-
export class XrayConfigService {
24+
export class XrayConfigService implements OnApplicationBootstrap {
2525
private readonly logger = new Logger(XrayConfigService.name);
2626

2727
constructor(
28-
private readonly xrayConfigRepository: XrayConfigRepository,
2928
private readonly commandBus: CommandBus,
3029
private readonly queryBus: QueryBus,
3130
private readonly eventBus: EventBus,
31+
private readonly xrayConfigRepository: XrayConfigRepository,
3232
) {}
3333

34+
async onApplicationBootstrap() {
35+
await this.syncInbounds();
36+
}
37+
3438
public async updateConfig(config: object): Promise<ICommandResponse<XrayConfigEntity>> {
3539
try {
3640
const existingConfig = await this.xrayConfigRepository.findFirst();
@@ -170,6 +174,7 @@ export class XrayConfigService {
170174
public async getConfigWithUsers(
171175
users: UserForConfigEntity[],
172176
): Promise<ICommandResponse<IXrayConfig>> {
177+
let configWithUsers: IXrayConfig | null = null;
173178
try {
174179
const config = await this.getConfig();
175180
if (!config.response) {
@@ -179,7 +184,7 @@ export class XrayConfigService {
179184
};
180185
}
181186
const parsedConf = new XRayConfig(config.response);
182-
const configWithUsers = parsedConf.prepareConfigForNode(users);
187+
configWithUsers = parsedConf.prepareConfigForNode(users);
183188
return {
184189
isOk: true,
185190
response: configWithUsers,
@@ -190,6 +195,8 @@ export class XrayConfigService {
190195
isOk: false,
191196
...ERRORS.GET_CONFIG_WITH_USERS_ERROR,
192197
};
198+
} finally {
199+
configWithUsers = null;
193200
}
194201
}
195202

0 commit comments

Comments
 (0)