Skip to content

Commit c9ca707

Browse files
committed
refactor: streamline user update process
1 parent 6d9690f commit c9ca707

File tree

2 files changed

+86
-126
lines changed

2 files changed

+86
-126
lines changed

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { TResetPeriods, TUsersStatus, USERS_STATUS } from '@contract/constants';
66
import { DB } from 'prisma/generated/types';
77

88
import { TransactionalAdapterPrisma } from '@nestjs-cls/transactional-adapter-prisma';
9-
import { TransactionHost } from '@nestjs-cls/transactional';
9+
import { Transactional, TransactionHost } from '@nestjs-cls/transactional';
1010
import { Injectable, Logger } from '@nestjs/common';
1111

1212
import { formatExecutionTime, getTime } from '@common/utils/get-elapsed-time';
@@ -478,15 +478,30 @@ export class UsersRepository {
478478
return [usersResult, Number(total.count)];
479479
}
480480

481-
public async update({ uuid, ...data }: Partial<BaseUserEntity>): Promise<BaseUserEntity> {
482-
const result = await this.prisma.tx.users.update({
483-
where: {
484-
uuid,
481+
@Transactional()
482+
public async update(
483+
dto: {
484+
tId: bigint;
485+
activeInternalSquads: string[];
486+
updateInternalSquads: boolean;
487+
} & Partial<BaseUserEntity>,
488+
): Promise<UserEntity | null> {
489+
const { tId, activeInternalSquads, updateInternalSquads, ...data } = dto;
490+
491+
await this.prisma.tx.users.update({
492+
select: {
493+
tId: true,
485494
},
495+
where: { tId },
486496
data,
487497
});
488498

489-
return this.userConverter.fromPrismaModelToEntity(result);
499+
if (updateInternalSquads) {
500+
await this.removeUserFromInternalSquads(tId);
501+
await this.addUserToInternalSquads(tId, activeInternalSquads);
502+
}
503+
504+
return await this.findUniqueByCriteria({ tId }, { activeInternalSquads: true });
490505
}
491506

492507
public async updateUserStatus(uuid: string, status: TUsersStatus): Promise<boolean> {
@@ -1086,7 +1101,14 @@ export class UsersRepository {
10861101
public async revokeUserSubscription(
10871102
dto: Pick<
10881103
BaseUserEntity,
1089-
'uuid' | 'trojanPassword' | 'vlessUuid' | 'ssPassword' | 'subRevokedAt' | 'shortUuid'
1104+
| 'uuid'
1105+
| 'trojanPassword'
1106+
| 'vlessUuid'
1107+
| 'ssPassword'
1108+
| 'subRevokedAt'
1109+
| 'shortUuid'
1110+
| 'subLastOpenedAt'
1111+
| 'subLastUserAgent'
10901112
>,
10911113
): Promise<boolean> {
10921114
const result = await this.qb.kysely
@@ -1097,6 +1119,8 @@ export class UsersRepository {
10971119
vlessUuid: getKyselyUuid(dto.vlessUuid),
10981120
ssPassword: dto.ssPassword,
10991121
shortUuid: dto.shortUuid,
1122+
subLastOpenedAt: dto.subLastOpenedAt,
1123+
subLastUserAgent: dto.subLastUserAgent,
11001124
})
11011125
.where('uuid', '=', getKyselyUuid(dto.uuid))
11021126
.executeTakeFirst();

src/modules/users/users.service.ts

Lines changed: 55 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import { Prisma } from '@prisma/client';
55
import { customAlphabet } from 'nanoid';
66
import dayjs from 'dayjs';
77

8-
import { CommandBus, EventBus, QueryBus } from '@nestjs/cqrs';
98
import { Inject, Injectable, Logger } from '@nestjs/common';
10-
import { Transactional } from '@nestjs-cls/transactional';
119
import { EventEmitter2 } from '@nestjs/event-emitter';
1210
import { CACHE_MANAGER } from '@nestjs/cache-manager';
11+
import { EventBus, QueryBus } from '@nestjs/cqrs';
1312
import { ConfigService } from '@nestjs/config';
1413

1514
import { wrapBigInt, wrapBigIntNullable } from '@common/utils';
@@ -54,7 +53,6 @@ export class UsersService {
5453

5554
constructor(
5655
private readonly userRepository: UsersRepository,
57-
private readonly commandBus: CommandBus,
5856
private readonly eventBus: EventBus,
5957
private readonly eventEmitter: EventEmitter2,
6058
private readonly queryBus: QueryBus,
@@ -72,9 +70,9 @@ export class UsersService {
7270
const userEntity = new BaseUserEntity({
7371
username: dto.username,
7472
shortUuid: dto.shortUuid || this.createNanoId(),
75-
trojanPassword: dto.trojanPassword || this.createTrojanPassword(),
73+
trojanPassword: dto.trojanPassword || this.createPassword(),
7674
vlessUuid: dto.vlessUuid || this.createUuid(),
77-
ssPassword: dto.ssPassword || this.createSSPassword(),
75+
ssPassword: dto.ssPassword || this.createPassword(),
7876
status: dto.status,
7977
trafficLimitBytes: wrapBigInt(dto.trafficLimitBytes),
8078
trafficLimitStrategy: dto.trafficLimitStrategy,
@@ -138,72 +136,13 @@ export class UsersService {
138136
}
139137

140138
public async updateUser(dto: UpdateUserRequestDto): Promise<TResult<UserEntity>> {
141-
try {
142-
const user = await this.updateUserTransactional(dto);
143-
144-
if (!user.isOk) return fail(ERRORS.UPDATE_USER_ERROR);
145-
146-
if (
147-
user.response.user.status === USERS_STATUS.ACTIVE &&
148-
user.response.isNeedToBeAddedToNode &&
149-
!user.response.isNeedToBeRemovedFromNode
150-
) {
151-
this.eventBus.publish(new AddUserToNodeEvent(user.response.user.uuid));
152-
}
153-
154-
if (user.response.isNeedToBeRemovedFromNode) {
155-
this.eventBus.publish(
156-
new RemoveUserFromNodeEvent(
157-
user.response.user.tId,
158-
user.response.user.vlessUuid,
159-
),
160-
);
161-
}
162-
163-
this.eventEmitter.emit(
164-
EVENTS.USER.MODIFIED,
165-
new UserEvent({
166-
user: user.response.user,
167-
event: EVENTS.USER.MODIFIED,
168-
}),
169-
);
170-
171-
await this.invalidateShortUuidRangeCache(user.response.user.shortUuid);
172-
173-
return ok(user.response.user);
174-
} catch (error) {
175-
if (error instanceof Error && error.message === ERRORS.USER_NOT_FOUND.code) {
176-
return fail(ERRORS.USER_NOT_FOUND);
177-
}
178-
179-
if (
180-
error instanceof Error &&
181-
error.message === ERRORS.CANT_GET_CREATED_USER_WITH_INBOUNDS.code
182-
) {
183-
return fail(ERRORS.CANT_GET_CREATED_USER_WITH_INBOUNDS);
184-
}
185-
186-
this.logger.error(error);
187-
188-
return fail(ERRORS.UPDATE_USER_ERROR);
189-
}
190-
}
191-
192-
@Transactional()
193-
public async updateUserTransactional(dto: UpdateUserRequestDto): Promise<
194-
TResult<{
195-
isNeedToBeAddedToNode: boolean;
196-
isNeedToBeRemovedFromNode: boolean;
197-
user: UserEntity;
198-
}>
199-
> {
200139
try {
201140
const {
202141
username,
203142
uuid,
204143
trafficLimitBytes,
205144
telegramId,
206-
activeInternalSquads,
145+
activeInternalSquads: newActiveInternalSquadsUuids,
207146
status,
208147
...rest
209148
} = dto;
@@ -214,28 +153,27 @@ export class UsersService {
214153
activeInternalSquads: true,
215154
});
216155

217-
if (!user) {
218-
throw new Error(ERRORS.USER_NOT_FOUND.code);
219-
}
156+
if (!user) return fail(ERRORS.USER_NOT_FOUND);
220157

221158
const newUserEntity = new BaseUserEntity({
222159
...rest,
223-
uuid: user.uuid,
160+
tId: user.tId,
224161
trafficLimitBytes: wrapBigInt(trafficLimitBytes),
225162
telegramId: wrapBigIntNullable(telegramId),
226163
lastTriggeredThreshold: trafficLimitBytes !== undefined ? 0 : undefined,
227164
});
228165

229-
let isNeedToBeAddedToNode = false;
230-
let isNeedToBeRemovedFromNode = false;
166+
let addToNode = false;
167+
let removeFromNode = false;
168+
let updateInternalSquads = false;
231169

232170
if (user.status !== 'ACTIVE' && status === 'ACTIVE') {
233-
isNeedToBeAddedToNode = true;
171+
addToNode = true;
234172
newUserEntity.status = 'ACTIVE';
235173
}
236174

237175
if (user.status === 'ACTIVE' && status === 'DISABLED') {
238-
isNeedToBeRemovedFromNode = true;
176+
removeFromNode = true;
239177
newUserEntity.status = 'DISABLED';
240178
}
241179

@@ -246,7 +184,7 @@ export class UsersService {
246184
trafficLimitBytes === 0
247185
) {
248186
newUserEntity.status = 'ACTIVE';
249-
isNeedToBeAddedToNode = true;
187+
addToNode = true;
250188
}
251189
}
252190
}
@@ -259,18 +197,15 @@ export class UsersService {
259197
if (!currentExpireDate.isSame(newExpireDate)) {
260198
if (newExpireDate.isAfter(now)) {
261199
newUserEntity.status = 'ACTIVE';
262-
isNeedToBeAddedToNode = true;
200+
addToNode = true;
263201
}
264202
}
265203
}
266204

267-
const result = await this.userRepository.update(newUserEntity);
268-
269-
if (activeInternalSquads) {
270-
const newActiveInternalSquadsUuids = activeInternalSquads;
271-
272-
const currentInternalSquadsUuids =
273-
user.activeInternalSquads.map((squad) => squad.uuid) || [];
205+
if (newActiveInternalSquadsUuids) {
206+
const currentInternalSquadsUuids = user.activeInternalSquads.map(
207+
(squad) => squad.uuid,
208+
);
274209

275210
const hasChanges =
276211
newActiveInternalSquadsUuids.length !== currentInternalSquadsUuids.length ||
@@ -279,41 +214,47 @@ export class UsersService {
279214
);
280215

281216
if (hasChanges) {
282-
await this.userRepository.removeUserFromInternalSquads(result.tId);
217+
updateInternalSquads = true;
218+
removeFromNode = newActiveInternalSquadsUuids.length === 0;
219+
addToNode = newActiveInternalSquadsUuids.length > 0;
220+
}
221+
}
283222

284-
if (newActiveInternalSquadsUuids.length === 0) {
285-
isNeedToBeRemovedFromNode = true;
286-
}
223+
const updatedUser = await this.userRepository.update({
224+
...newUserEntity,
225+
activeInternalSquads: newActiveInternalSquadsUuids || [],
226+
updateInternalSquads,
227+
});
228+
229+
if (!updatedUser) {
230+
return fail(ERRORS.UPDATE_USER_ERROR);
231+
}
287232

288-
if (newActiveInternalSquadsUuids.length > 0) {
289-
await this.userRepository.addUserToInternalSquads(
290-
result.tId,
291-
newActiveInternalSquadsUuids,
292-
);
233+
if (updatedUser.status === USERS_STATUS.ACTIVE && addToNode && !removeFromNode) {
234+
this.eventBus.publish(new AddUserToNodeEvent(updatedUser.uuid));
235+
}
293236

294-
isNeedToBeAddedToNode = true;
295-
}
296-
}
237+
if (removeFromNode) {
238+
this.eventBus.publish(
239+
new RemoveUserFromNodeEvent(updatedUser.tId, updatedUser.vlessUuid),
240+
);
297241
}
298242

299-
const userWithInbounds = await this.userRepository.findUniqueByCriteria(
300-
{ tId: result.tId },
301-
{
302-
activeInternalSquads: true,
303-
},
243+
this.eventEmitter.emit(
244+
EVENTS.USER.MODIFIED,
245+
new UserEvent({
246+
user: updatedUser,
247+
event: EVENTS.USER.MODIFIED,
248+
}),
304249
);
305250

306-
if (!userWithInbounds) {
307-
throw new Error(ERRORS.CANT_GET_CREATED_USER_WITH_INBOUNDS.code);
308-
}
251+
await this.invalidateShortUuidRangeCache(updatedUser.shortUuid);
309252

310-
return ok({
311-
user: userWithInbounds,
312-
isNeedToBeAddedToNode,
313-
isNeedToBeRemovedFromNode,
314-
});
253+
return ok(updatedUser);
315254
} catch (error) {
316-
throw error;
255+
this.logger.error(error);
256+
257+
return fail(ERRORS.UPDATE_USER_ERROR);
317258
}
318259
}
319260

@@ -383,10 +324,12 @@ export class UsersService {
383324
const updateResult = await this.userRepository.revokeUserSubscription({
384325
uuid: user.uuid,
385326
shortUuid: shortUuid ?? this.createNanoId(),
386-
trojanPassword: this.createTrojanPassword(),
327+
trojanPassword: this.createPassword(),
387328
vlessUuid: this.createUuid(),
388-
ssPassword: this.createTrojanPassword(),
329+
ssPassword: this.createPassword(),
389330
subRevokedAt: new Date(),
331+
subLastOpenedAt: null,
332+
subLastUserAgent: null,
390333
});
391334

392335
if (!updateResult) return fail(ERRORS.REVOKE_USER_SUBSCRIPTION_ERROR);
@@ -867,16 +810,9 @@ export class UsersService {
867810
return nanoid();
868811
}
869812

870-
private createTrojanPassword(): string {
871-
const alphabet = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghjkmnopqrstuvwxyz-';
872-
const nanoid = customAlphabet(alphabet, 30);
873-
874-
return nanoid();
875-
}
876-
877-
private createSSPassword(): string {
813+
private createPassword(length: number = 32): string {
878814
const alphabet = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghjkmnopqrstuvwxyz-';
879-
const nanoid = customAlphabet(alphabet, 32);
815+
const nanoid = customAlphabet(alphabet, length);
880816

881817
return nanoid();
882818
}

0 commit comments

Comments
 (0)