Skip to content
This repository was archived by the owner on Feb 24, 2026. It is now read-only.

Commit a8e21b5

Browse files
authored
feat: create real timereaction to permission and feature changes (#155)
1 parent 7004e83 commit a8e21b5

32 files changed

Lines changed: 580 additions & 238 deletions

apps/gauzy/src/app/app.component.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import {
1717
DEFAULT_DATE_PICKER_CONFIG,
1818
DEFAULT_SELECTOR_VISIBILITY,
1919
DateRangePickerBuilderService,
20+
FeatureSocketService,
2021
IDatePickerConfig,
2122
ISelectorVisibility,
2223
JitsuService,
2324
LanguagesService,
2425
NavigationService,
26+
RolePermissionSocketService,
2527
SelectorBuilderService,
2628
SeoService,
2729
Store,
@@ -52,7 +54,9 @@ export class AppComponent implements OnInit, AfterViewInit {
5254
private readonly _selectorBuilderService: SelectorBuilderService,
5355
private readonly _dateRangePickerBuilderService: DateRangePickerBuilderService,
5456
private readonly _navigationService: NavigationService,
55-
private readonly _usersSocketService: UsersSocketService
57+
private readonly _usersSocketService: UsersSocketService,
58+
private readonly rolePermissionSocketService: RolePermissionSocketService,
59+
private readonly _featureSocketService: FeatureSocketService
5660
) {
5761
this.getActivateRouterDataEvent();
5862
this.getPreferredLanguage();
@@ -115,6 +119,26 @@ export class AppComponent implements OnInit, AfterViewInit {
115119
if (Number(this._store.serverConnection) === 0) {
116120
this.loading = false;
117121
}
122+
123+
this.rolePermissionSocketService.permissionChanged$
124+
.pipe(
125+
filter(Boolean),
126+
tap(() => {
127+
window.location.reload();
128+
}),
129+
untilDestroyed(this)
130+
)
131+
.subscribe();
132+
133+
this._featureSocketService.featureChanged$
134+
.pipe(
135+
filter(Boolean),
136+
tap(() => {
137+
window.location.reload();
138+
}),
139+
untilDestroyed(this)
140+
)
141+
.subscribe();
118142
}
119143

120144
/**

packages/core/src/lib/auth/auth.service.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ export class AuthService extends SocialAuthService {
405405
private generateToken(user: IUser, code: string): string {
406406
const payload: JwtPayload = {
407407
userId: user.id,
408+
roleId: user.role ? user.role.id : null,
408409
email: user.email,
409410
tenantId: user.tenant ? user.tenantId : null,
410411
code
@@ -976,6 +977,7 @@ export class AuthService extends SocialAuthService {
976977
id: user.id,
977978
tenantId: user.tenantId ?? null,
978979
employeeId: employee ? employee.id : null,
980+
roleId: user.role ? user.role.id : null,
979981
role: user.role ? user.role.name : null,
980982
permissions: user.role?.rolePermissions?.filter((rp) => rp.enabled).map((rp) => rp.permission) ?? null
981983
};
@@ -1011,7 +1013,8 @@ export class AuthService extends SocialAuthService {
10111013
id: user.id,
10121014
email: user.email,
10131015
tenantId: user.tenantId || null,
1014-
role: user.role ? user.role.name : null
1016+
role: user.role ? user.role.name : null,
1017+
roleId: user.role ? user.role.id : null
10151018
};
10161019

10171020
// Generate the JWT refresh token

packages/core/src/lib/core/entities/index.ts

Lines changed: 160 additions & 162 deletions
Large diffs are not rendered by default.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
2+
import { EmployeeSocketEvent } from '../../../event-bus';
3+
import { SocketService } from '../../../socket';
4+
import { EmployeeService } from '../../employee.service';
5+
6+
@EventsHandler(EmployeeSocketEvent)
7+
export class EmployeeSocketEventHandler implements IEventHandler<EmployeeSocketEvent> {
8+
constructor(private readonly _socketService: SocketService, private readonly _employeeService: EmployeeService) {}
9+
10+
/**
11+
* Handles EmployeeSocketEvent.
12+
* The event contains employee IDs, socket event name and payload.
13+
* This handler resolves related users and emits a socket event to each connected user.
14+
*/
15+
async handle(event: EmployeeSocketEvent) {
16+
const employees = await this._employeeService.findByIds(event.employeeIds);
17+
18+
employees
19+
.map((e) => e.user?.id)
20+
.filter(Boolean)
21+
.forEach((userId) => this._socketService.emitToClient(userId, event.event, event.payload));
22+
}
23+
}

packages/core/src/lib/employee/commands/handlers/employee.update.handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ export class EmployeeUpdateHandler implements ICommandHandler<EmployeeUpdateComm
4848

4949
// Send a real-time event to the specified user via socket.
5050
// No error is thrown if the user is not currently connected.
51-
this._socketService.sendTimerChanged(employee?.id);
52-
this._socketService.emitToClient(employee?.id, 'employee:changed', existingEmployee);
51+
this._socketService.notifyEmployee(employee?.id, 'timer:changed');
52+
this._socketService.notifyEmployee(employee?.id, 'employee:changed', existingEmployee);
5353

5454
return employee;
5555
} catch (error) {
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EmployeeBulkCreateHandler } from './employee.bulk.create.handler';
22
import { EmployeeCreateHandler } from './employee.create.handler';
33
import { EmployeeGetHandler } from './employee.get.handler';
4+
import { EmployeeSocketEventHandler } from './employee.socket.handler';
45
import { EmployeeUpdateHandler } from './employee.update.handler';
56
import { WorkingEmployeeGetHandler } from './working-employee.get.handler';
67

@@ -9,5 +10,6 @@ export const CommandHandlers = [
910
EmployeeBulkCreateHandler,
1011
EmployeeGetHandler,
1112
EmployeeUpdateHandler,
12-
WorkingEmployeeGetHandler
13+
WorkingEmployeeGetHandler,
14+
EmployeeSocketEventHandler
1315
];

packages/core/src/lib/employee/employee.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { SocketModule } from '../socket/socket.module';
2626
forwardRef(() => UserModule),
2727
forwardRef(() => AuthModule),
2828
SocketModule,
29-
RoleModule,
29+
forwardRef(() => RoleModule),
3030
CqrsModule
3131
],
3232
controllers: [EmployeeController],

packages/core/src/lib/employee/employee.service.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ export class EmployeeService extends TenantAwareCrudService<Employee> {
469469

470470
// Tables joins with relations
471471
query.leftJoin(`${query.alias}.user`, 'user');
472+
query.leftJoin('user.organizations', 'userOrganization');
472473
query.leftJoin(`${query.alias}.tags`, 'tags');
473474
query.leftJoinAndSelect(`${query.alias}.organizationEmploymentTypes`, 'organizationEmploymentTypes');
474475

@@ -511,8 +512,9 @@ export class EmployeeService extends TenantAwareCrudService<Employee> {
511512
web.andWhere(p(`"${qb.alias}"."tenantId" = :tenantId`), { tenantId });
512513

513514
if (isNotEmpty(where?.organizationId)) {
514-
const organizationId = where.organizationId;
515-
web.andWhere(p(`"${qb.alias}"."organizationId" = :organizationId`), { organizationId });
515+
web.andWhere(`"userOrganization"."organizationId" = :organizationId`, {
516+
organizationId: where.organizationId
517+
});
516518
}
517519
})
518520
);
@@ -699,4 +701,47 @@ export class EmployeeService extends TenantAwareCrudService<Employee> {
699701
return null;
700702
}
701703
}
704+
705+
async findByIds(employeeIds: string[], options?: FindManyOptions<Employee>): Promise<IEmployee[]> {
706+
try {
707+
if (!employeeIds.length) {
708+
return [];
709+
}
710+
711+
const where = {
712+
id: In(employeeIds),
713+
isActive: true,
714+
isArchived: false
715+
};
716+
717+
const queryOptions: FindManyOptions<Employee> = {
718+
...options,
719+
where: {
720+
...where,
721+
...(options?.where || {})
722+
},
723+
relations: {
724+
user: true,
725+
...(options?.relations || {})
726+
}
727+
};
728+
729+
switch (this.ormType) {
730+
case MultiORMEnum.MikroORM: {
731+
const { where: mikroWhere, mikroOptions } = parseTypeORMFindToMikroOrm<Employee>(queryOptions);
732+
const employees = await this.mikroOrmRepository.find(mikroWhere, mikroOptions);
733+
return employees.map((e) => this.serialize(e as Employee));
734+
}
735+
736+
case MultiORMEnum.TypeORM:
737+
return await this.typeOrmRepository.find(queryOptions);
738+
739+
default:
740+
throw new Error(`Not implemented for ORM type: ${this.ormType}`);
741+
}
742+
} catch (error) {
743+
this.logger.error('Error finding employees by IDs', error);
744+
return [];
745+
}
746+
}
702747
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export class EmployeeSocketEvent {
2+
constructor(public readonly employeeIds: string[], public readonly event: string, public readonly payload?: any) {}
3+
}

packages/core/src/lib/event-bus/events/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './account-verified.event';
33
export * from './integration.event';
44
export * from './task.event';
55
export * from './screenshot.event';
6+
export * from './employee-socket.event';

0 commit comments

Comments
 (0)