Skip to content

Commit fcf97bc

Browse files
authored
Merge pull request #2 from Gickrede-e/aishit/modify-remnawave-for-multiple-inbounds
Allow multiple inbounds per host
2 parents 0f344f3 + 3caab17 commit fcf97bc

File tree

9 files changed

+222
-63
lines changed

9 files changed

+222
-63
lines changed

libs/contract/commands/hosts/create.command.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ export namespace CreateHostCommand {
1515
);
1616

1717
export const RequestSchema = z.object({
18-
inbound: z.object({
19-
configProfileUuid: z.string().uuid(),
20-
configProfileInboundUuid: z.string().uuid(),
21-
}),
18+
inbounds: z
19+
.array(
20+
z.object({
21+
configProfileUuid: z.string().uuid(),
22+
configProfileInboundUuid: z.string().uuid(),
23+
}),
24+
)
25+
.min(1, 'At least one inbound is required'),
2226
remark: z
2327
.string({
2428
invalid_type_error: 'Remark must be a string',

libs/contract/commands/hosts/update.command.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ export namespace UpdateHostCommand {
1717
export const RequestSchema = HostsSchema.pick({
1818
uuid: true,
1919
}).extend({
20-
inbound: z
21-
.object({
22-
configProfileUuid: z.string().uuid(),
23-
configProfileInboundUuid: z.string().uuid(),
24-
})
20+
inbounds: z
21+
.array(
22+
z.object({
23+
configProfileUuid: z.string().uuid(),
24+
configProfileInboundUuid: z.string().uuid(),
25+
}),
26+
)
27+
.min(1)
2528
.optional(),
2629
remark: z
2730
.string({

libs/contract/models/hosts.schema.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ export const HostsSchema = z.object({
1919
muxParams: z.nullable(z.unknown()),
2020
sockoptParams: z.nullable(z.unknown()),
2121

22-
inbound: z.object({
23-
configProfileUuid: z.string().uuid().nullable(),
24-
configProfileInboundUuid: z.string().uuid().nullable(),
25-
}),
22+
inbounds: z
23+
.array(
24+
z.object({
25+
configProfileUuid: z.string().uuid(),
26+
configProfileInboundUuid: z.string().uuid(),
27+
}),
28+
)
29+
.default([]),
2630

2731
serverDescription: z.string().max(30).nullable(),
2832
tag: z.string().nullable(),

prisma/schema.prisma

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,24 @@ model Hosts {
298298
configProfiles ConfigProfiles? @relation(fields: [configProfileUuid], references: [uuid], onDelete: SetNull)
299299
xrayJsonTemplate SubscriptionTemplate? @relation(fields: [xrayJsonTemplateUuid], references: [uuid], onDelete: SetNull)
300300
301+
hostInbounds HostInbounds[]
301302
nodes HostsToNodes[]
302303
excludedInternalSquads InternalSquadHostExclusions[]
303304
304305
@@map("hosts")
305306
}
306307

308+
model HostInbounds {
309+
hostUuid String @map("host_uuid") @db.Uuid
310+
configProfileInboundUuid String @map("config_profile_inbound_uuid") @db.Uuid
311+
312+
host Hosts @relation(fields: [hostUuid], references: [uuid], onDelete: Cascade)
313+
configProfileInbounds ConfigProfileInbounds @relation(fields: [configProfileInboundUuid], references: [uuid], onDelete: Cascade)
314+
315+
@@id([hostUuid, configProfileInboundUuid])
316+
@@map("host_inbounds")
317+
}
318+
307319
model InternalSquadHostExclusions {
308320
hostUuid String @map("host_uuid") @db.Uuid
309321
squadUuid String @map("squad_uuid") @db.Uuid

src/modules/hosts/entities/hosts.entity.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export class HostsEntity implements Hosts {
3232

3333
configProfileUuid: string | null;
3434
configProfileInboundUuid: string | null;
35+
configProfileInboundUuids: string[];
36+
configProfileInboundMappings?: { configProfileInboundUuid: string; configProfileUuid: string }[];
3537

3638
xrayJsonTemplateUuid: string | null;
3739

@@ -45,5 +47,13 @@ export class HostsEntity implements Hosts {
4547

4648
constructor(data: Partial<Hosts>) {
4749
Object.assign(this, data);
50+
51+
if (!this.configProfileInboundUuids) {
52+
this.configProfileInboundUuids = [];
53+
}
54+
55+
if (!this.configProfileInboundMappings) {
56+
this.configProfileInboundMappings = [];
57+
}
4858
}
4959
}

src/modules/hosts/hosts.converter.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,20 @@ import { UniversalConverter } from '@common/converter/universalConverter';
66

77
import { HostsEntity } from './entities/hosts.entity';
88

9-
const modelToEntity = (model: Hosts): HostsEntity => {
10-
return new HostsEntity(model);
9+
type HostsWithRelations = Hosts & {
10+
hostInbounds?: { configProfileInboundUuid: string; configProfileInbounds: { configProfileUuid: string } }[];
11+
};
12+
13+
const modelToEntity = (model: HostsWithRelations): HostsEntity => {
14+
return new HostsEntity({
15+
...model,
16+
configProfileInboundUuids: model.hostInbounds?.map((i) => i.configProfileInboundUuid) || [],
17+
configProfileInboundMappings:
18+
model.hostInbounds?.map((i) => ({
19+
configProfileInboundUuid: i.configProfileInboundUuid,
20+
configProfileUuid: i.configProfileInbounds.configProfileUuid,
21+
})) || [],
22+
});
1123
};
1224

1325
const entityToModel = (entity: HostsEntity): Hosts => {

src/modules/hosts/hosts.service.ts

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -82,31 +82,53 @@ export class HostsService {
8282
serverDescription = undefined;
8383
}
8484

85-
const { inbound: inboundObj, nodes, excludedInternalSquads, ...rest } = dto;
85+
const { inbounds, nodes, excludedInternalSquads, ...rest } = dto;
8686

87-
const configProfile = await this.queryBus.execute(
88-
new GetConfigProfileByUuidQuery(inboundObj.configProfileUuid),
89-
);
87+
const validatedInbounds = [] as { configProfileUuid: string; configProfileInboundUuid: string }[];
88+
const configProfileCache = new Map<string, Awaited<ReturnType<typeof this.queryBus.execute>>>();
9089

91-
if (!configProfile.isOk) {
92-
return fail(ERRORS.CONFIG_PROFILE_NOT_FOUND);
93-
}
90+
for (const inbound of inbounds) {
91+
if (!configProfileCache.has(inbound.configProfileUuid)) {
92+
const configProfile = await this.queryBus.execute(
93+
new GetConfigProfileByUuidQuery(inbound.configProfileUuid),
94+
);
95+
configProfileCache.set(inbound.configProfileUuid, configProfile);
96+
}
9497

95-
const configProfileInbound = configProfile.response.inbounds.find(
96-
(inbound) => inbound.uuid === inboundObj.configProfileInboundUuid,
97-
);
98-
if (!configProfileInbound) {
99-
return fail(ERRORS.CONFIG_PROFILE_INBOUND_NOT_FOUND_IN_SPECIFIED_PROFILE);
98+
const configProfile = configProfileCache.get(inbound.configProfileUuid)!;
99+
100+
if (!configProfile.isOk) {
101+
return fail(ERRORS.CONFIG_PROFILE_NOT_FOUND);
102+
}
103+
104+
const configProfileInbound = configProfile.response.inbounds.find(
105+
(profileInbound) => profileInbound.uuid === inbound.configProfileInboundUuid,
106+
);
107+
if (!configProfileInbound) {
108+
return fail(ERRORS.CONFIG_PROFILE_INBOUND_NOT_FOUND_IN_SPECIFIED_PROFILE);
109+
}
110+
111+
validatedInbounds.push({
112+
configProfileUuid: inbound.configProfileUuid,
113+
configProfileInboundUuid: inbound.configProfileInboundUuid,
114+
});
100115
}
101116

117+
const primaryInbound = validatedInbounds[0];
118+
102119
const hostEntity = new HostsEntity({
103120
...rest,
104121
address: dto.address.trim(),
105122
xHttpExtraParams,
106123
muxParams,
107124
sockoptParams,
108-
configProfileUuid: configProfile.response.uuid,
109-
configProfileInboundUuid: configProfileInbound.uuid,
125+
configProfileUuid: primaryInbound.configProfileUuid,
126+
configProfileInboundUuid: primaryInbound.configProfileInboundUuid,
127+
configProfileInboundUuids: validatedInbounds.map((inbound) => inbound.configProfileInboundUuid),
128+
configProfileInboundMappings: validatedInbounds.map((inbound) => ({
129+
configProfileInboundUuid: inbound.configProfileInboundUuid,
130+
configProfileUuid: inbound.configProfileUuid,
131+
})),
110132
serverDescription,
111133
});
112134

@@ -143,7 +165,7 @@ export class HostsService {
143165

144166
public async updateHost(dto: UpdateHostRequestDto): Promise<TResult<HostsEntity>> {
145167
try {
146-
const { inbound: inboundObj, nodes, excludedInternalSquads, ...rest } = dto;
168+
const { inbounds, nodes, excludedInternalSquads, ...rest } = dto;
147169

148170
const host = await this.hostsRepository.findByUUID(dto.uuid);
149171
if (!host) return fail(ERRORS.HOST_NOT_FOUND);
@@ -208,25 +230,51 @@ export class HostsService {
208230

209231
let configProfileUuid: string | undefined;
210232
let configProfileInboundUuid: string | undefined;
211-
if (inboundObj) {
212-
const configProfile = await this.queryBus.execute(
213-
new GetConfigProfileByUuidQuery(inboundObj.configProfileUuid),
214-
);
215-
216-
if (!configProfile.isOk) {
217-
return fail(ERRORS.CONFIG_PROFILE_NOT_FOUND);
218-
}
219-
220-
const configProfileInbound = configProfile.response.inbounds.find(
221-
(inbound) => inbound.uuid === inboundObj.configProfileInboundUuid,
222-
);
223-
224-
if (!configProfileInbound) {
225-
return fail(ERRORS.CONFIG_PROFILE_INBOUND_NOT_FOUND_IN_SPECIFIED_PROFILE);
233+
let configProfileInboundUuids: string[] | undefined;
234+
let configProfileInboundMappings:
235+
| { configProfileInboundUuid: string; configProfileUuid: string }[]
236+
| undefined;
237+
238+
if (inbounds) {
239+
const validatedInbounds = [] as { configProfileUuid: string; configProfileInboundUuid: string }[];
240+
const configProfileCache = new Map<string, Awaited<ReturnType<typeof this.queryBus.execute>>>();
241+
242+
for (const inbound of inbounds) {
243+
if (!configProfileCache.has(inbound.configProfileUuid)) {
244+
const configProfile = await this.queryBus.execute(
245+
new GetConfigProfileByUuidQuery(inbound.configProfileUuid),
246+
);
247+
configProfileCache.set(inbound.configProfileUuid, configProfile);
248+
}
249+
250+
const configProfile = configProfileCache.get(inbound.configProfileUuid)!;
251+
252+
if (!configProfile.isOk) {
253+
return fail(ERRORS.CONFIG_PROFILE_NOT_FOUND);
254+
}
255+
256+
const configProfileInbound = configProfile.response.inbounds.find(
257+
(profileInbound) => profileInbound.uuid === inbound.configProfileInboundUuid,
258+
);
259+
260+
if (!configProfileInbound) {
261+
return fail(ERRORS.CONFIG_PROFILE_INBOUND_NOT_FOUND_IN_SPECIFIED_PROFILE);
262+
}
263+
264+
validatedInbounds.push({
265+
configProfileUuid: inbound.configProfileUuid,
266+
configProfileInboundUuid: inbound.configProfileInboundUuid,
267+
});
226268
}
227269

228-
configProfileUuid = configProfile.response.uuid;
229-
configProfileInboundUuid = configProfileInbound.uuid;
270+
const primaryInbound = validatedInbounds[0];
271+
configProfileUuid = primaryInbound.configProfileUuid;
272+
configProfileInboundUuid = primaryInbound.configProfileInboundUuid;
273+
configProfileInboundUuids = validatedInbounds.map((inbound) => inbound.configProfileInboundUuid);
274+
configProfileInboundMappings = validatedInbounds.map((inbound) => ({
275+
configProfileInboundUuid: inbound.configProfileInboundUuid,
276+
configProfileUuid: inbound.configProfileUuid,
277+
}));
230278
}
231279

232280
if (nodes !== undefined) {
@@ -250,6 +298,8 @@ export class HostsService {
250298
sockoptParams,
251299
configProfileUuid,
252300
configProfileInboundUuid,
301+
configProfileInboundUuids,
302+
configProfileInboundMappings,
253303
serverDescription,
254304
});
255305

src/modules/hosts/models/host.response.model.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ export class HostResponseModel {
3333
public keepSniBlank: boolean;
3434
public vlessRouteId: number | null;
3535

36-
public inbound: {
37-
configProfileUuid: string | null;
38-
configProfileInboundUuid: string | null;
39-
};
36+
public inbounds: {
37+
configProfileUuid: string;
38+
configProfileInboundUuid: string;
39+
}[];
4040

4141
public nodes: string[];
4242

@@ -73,10 +73,16 @@ export class HostResponseModel {
7373
this.overrideSniFromAddress = data.overrideSniFromAddress;
7474
this.keepSniBlank = data.keepSniBlank;
7575
this.vlessRouteId = data.vlessRouteId;
76-
this.inbound = {
77-
configProfileUuid: data.configProfileUuid,
78-
configProfileInboundUuid: data.configProfileInboundUuid,
79-
};
76+
this.inbounds = data.configProfileInboundMappings?.length
77+
? data.configProfileInboundMappings
78+
: data.configProfileInboundUuid && data.configProfileUuid
79+
? [
80+
{
81+
configProfileInboundUuid: data.configProfileInboundUuid,
82+
configProfileUuid: data.configProfileUuid,
83+
},
84+
]
85+
: [];
8086

8187
this.nodes = data.nodes.map((node) => node.nodeUuid);
8288
this.excludedInternalSquads = data.excludedInternalSquads.map(

0 commit comments

Comments
 (0)