Skip to content

Commit f5a463c

Browse files
committed
feat: add support for disabled hosts in raw sub
Adds ability to include disabled hosts in raw subscription responses via query parameter - Introduces new `withDisabledHosts` query parameter for raw subscription endpoint - Updates repository layer to conditionally include disabled hosts - Adds host database metadata to subscription response - Makes HWID device limit checks configurable in API requests
1 parent d168abb commit f5a463c

File tree

13 files changed

+114
-21
lines changed

13 files changed

+114
-21
lines changed

libs/contract/commands/subscription/get-raw-subscription-by-short-uuid.command.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ export namespace GetRawSubscriptionByShortUuidCommand {
2020

2121
export type Request = z.infer<typeof RequestSchema>;
2222

23+
export const RequestQuerySchema = z.object({
24+
withDisabledHosts: z
25+
.string()
26+
.transform((str) => str === 'true')
27+
.optional()
28+
.default('false'),
29+
});
30+
31+
export type RequestQuery = z.infer<typeof RequestQuerySchema>;
32+
2333
export const ResponseSchema = z.object({
2434
response: z.object({
2535
user: z.object({
@@ -81,6 +91,17 @@ export namespace GetRawSubscriptionByShortUuidCommand {
8191
}),
8292
),
8393
),
94+
dbData: z.optional(
95+
z.object({
96+
rawInbound: z.nullable(z.object({})),
97+
tag: z.string(),
98+
uuid: z.string(),
99+
configProfileUuid: z.nullable(z.string()),
100+
configProfileInboundUuid: z.nullable(z.string()),
101+
isDisabled: z.boolean(),
102+
viewPosition: z.number(),
103+
}),
104+
),
84105
}),
85106
),
86107
headers: z.record(z.string(), z.string()),

libs/contract/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@remnawave/backend-contract",
3-
"version": "2.1.1",
3+
"version": "2.1.2",
44
"public": true,
55
"license": "AGPL-3.0-only",
66
"description": "A contract library for Remnawave Backend. It can be used in backend and frontend.",

src/modules/hosts/queries/get-hosts-for-user/get-hosts-for-user.handler.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export class GetHostsForUserHandler
1818

1919
async execute(query: GetHostsForUserQuery): Promise<ICommandResponse<HostsEntity[]>> {
2020
try {
21-
const hosts = await this.hostsRepository.findActiveHostsByUserUuid(query.userUuid);
21+
const hosts = await this.hostsRepository.findActiveHostsByUserUuid(
22+
query.userUuid,
23+
query.returnDisabledHosts,
24+
);
2225

2326
return {
2427
isOk: true,
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export class GetHostsForUserQuery {
2-
constructor(public readonly userUuid: string) {}
2+
constructor(
3+
public readonly userUuid: string,
4+
public readonly returnDisabledHosts?: boolean,
5+
) {}
36
}

src/modules/hosts/repositories/hosts.repository.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ export class HostsRepository implements ICrud<HostsEntity> {
130130
return !!result;
131131
}
132132

133-
public async findActiveHostsByUserUuid(userUuid: string): Promise<HostWithRawInbound[]> {
133+
public async findActiveHostsByUserUuid(
134+
userUuid: string,
135+
returnDisabledHosts: boolean = false,
136+
): Promise<HostWithRawInbound[]> {
134137
const hosts = await this.qb.kysely
135138
.selectFrom('hosts')
136139
.distinct()
@@ -149,7 +152,7 @@ export class HostsRepository implements ICrud<HostsEntity> {
149152
'configProfileInbounds.uuid',
150153
'hosts.configProfileInboundUuid',
151154
)
152-
.where('hosts.isDisabled', '=', false)
155+
.$if(!returnDisabledHosts, (eb) => eb.where('hosts.isDisabled', '=', false))
153156
.where('internalSquadMembers.userUuid', '=', getKyselyUuid(userUuid))
154157
.selectAll('hosts')
155158
.select(['configProfileInbounds.rawInbound', 'configProfileInbounds.tag'])

src/modules/hwid-user-devices/hwid-user-devices.service.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,20 @@ import { CreateUserHwidDeviceRequestDto } from './dtos';
1414
@Injectable()
1515
export class HwidUserDevicesService {
1616
private readonly logger = new Logger(HwidUserDevicesService.name);
17+
private readonly hwidDeviceLimitEnabled: boolean;
18+
private readonly hwidGlobalDeviceLimit: number | undefined;
1719

1820
constructor(
1921
private readonly hwidUserDevicesRepository: HwidUserDevicesRepository,
2022
private readonly configService: ConfigService,
2123
private readonly queryBus: QueryBus,
22-
) {}
24+
) {
25+
this.hwidDeviceLimitEnabled =
26+
this.configService.getOrThrow<string>('HWID_DEVICE_LIMIT_ENABLED') === 'true';
27+
this.hwidGlobalDeviceLimit = this.configService.get<number | undefined>(
28+
'HWID_FALLBACK_DEVICE_LIMIT',
29+
);
30+
}
2331

2432
public async createUserHwidDevice(
2533
dto: CreateUserHwidDeviceRequestDto,
@@ -44,10 +52,6 @@ export class HwidUserDevicesService {
4452
};
4553
}
4654

47-
const hwidGlobalDeviceLimit = this.configService.getOrThrow<number>(
48-
'HWID_FALLBACK_DEVICE_LIMIT',
49-
);
50-
5155
const isDeviceExists = await this.hwidUserDevicesRepository.checkHwidExists(
5256
dto.hwid,
5357
dto.userUuid,
@@ -60,15 +64,17 @@ export class HwidUserDevicesService {
6064
};
6165
}
6266

63-
const count = await this.hwidUserDevicesRepository.countByUserUuid(dto.userUuid);
67+
if (this.hwidDeviceLimitEnabled && this.hwidGlobalDeviceLimit) {
68+
const count = await this.hwidUserDevicesRepository.countByUserUuid(dto.userUuid);
6469

65-
const deviceLimit = user.response.hwidDeviceLimit ?? hwidGlobalDeviceLimit;
70+
const deviceLimit = user.response.hwidDeviceLimit ?? this.hwidGlobalDeviceLimit;
6671

67-
if (count >= deviceLimit) {
68-
return {
69-
isOk: false,
70-
...ERRORS.USER_HWID_DEVICE_LIMIT_REACHED,
71-
};
72+
if (count >= deviceLimit) {
73+
return {
74+
isOk: false,
75+
...ERRORS.USER_HWID_DEVICE_LIMIT_REACHED,
76+
};
77+
}
7278
}
7379

7480
await this.hwidUserDevicesRepository.create(new HwidUserDeviceEntity(dto));

src/modules/subscription-template/generators/format-hosts.service.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class FormatHostsService {
4242
public async generateFormattedHosts(
4343
hosts: HostWithRawInbound[],
4444
user: UserEntity,
45+
returnDbHost: boolean = false,
4546
): Promise<IFormattedHost[]> {
4647
const formattedHosts: IFormattedHost[] = [];
4748

@@ -319,6 +320,20 @@ export class FormatHostsService {
319320

320321
const spiderX = spiderXFromConfig || '';
321322

323+
let dbData: IFormattedHost['dbData'] | undefined;
324+
325+
if (returnDbHost) {
326+
dbData = {
327+
rawInbound: inputHost.rawInbound,
328+
tag: inputHost.tag,
329+
uuid: inputHost.uuid,
330+
configProfileUuid: inputHost.configProfileUuid,
331+
configProfileInboundUuid: inputHost.configProfileInboundUuid,
332+
isDisabled: inputHost.isDisabled,
333+
viewPosition: inputHost.viewPosition,
334+
};
335+
}
336+
322337
formattedHosts.push({
323338
remark,
324339
address,
@@ -345,6 +360,7 @@ export class FormatHostsService {
345360
serverDescription,
346361
muxParams,
347362
sockoptParams,
363+
dbData,
348364
});
349365
}
350366

src/modules/subscription-template/generators/interfaces/formatted-hosts.interface.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@ export interface IFormattedHost {
2929
muxParams?: null | object;
3030
sockoptParams?: null | object;
3131
serverDescription?: string;
32+
dbData?: {
33+
rawInbound: object | null;
34+
tag: string;
35+
uuid: string;
36+
configProfileUuid: string | null;
37+
configProfileInboundUuid: string | null;
38+
isDisabled: boolean;
39+
viewPosition: number;
40+
};
3241
}

src/modules/subscription-template/generators/interfaces/raw-host.interface.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { StreamSettingsObject } from '@common/helpers/xray-config/interfaces/transport.config';
22

33
export interface IRawHost {
4+
dbData?: {
5+
rawInbound: object | null;
6+
tag: string;
7+
uuid: string;
8+
configProfileUuid: string | null;
9+
configProfileInboundUuid: string | null;
10+
isDisabled: boolean;
11+
viewPosition: number;
12+
};
13+
414
address: string;
515
alpn: string;
616
fingerprint: string;

src/modules/subscription-template/render-templates.service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ export class RenderTemplatesService {
118118
}> {
119119
const { user, hosts } = params;
120120

121-
const formattedHosts = await this.formatHostsService.generateFormattedHosts(hosts, user);
121+
const formattedHosts = await this.formatHostsService.generateFormattedHosts(
122+
hosts,
123+
user,
124+
true,
125+
);
122126

123127
const rawHosts = await this.rawHostsGeneratorService.generateConfig(formattedHosts);
124128

0 commit comments

Comments
 (0)