feat: add user preferences for online status and read receipts with real-time updates
- Introduced `showOnlineStatus` and `showReadReceipts` fields in the user schema and API. - Integrated real-time status broadcasting in `UsersService` via `EventsGateway`. - Updated repository and frontend user types to align with new fields. - Enhanced user update handling to support dynamic preference changes for online status.
This commit is contained in:
@@ -36,6 +36,8 @@ export const users = pgTable(
|
|||||||
// Sécurité
|
// Sécurité
|
||||||
twoFactorSecret: pgpEncrypted("two_factor_secret"),
|
twoFactorSecret: pgpEncrypted("two_factor_secret"),
|
||||||
isTwoFactorEnabled: boolean("is_two_factor_enabled").notNull().default(false),
|
isTwoFactorEnabled: boolean("is_two_factor_enabled").notNull().default(false),
|
||||||
|
showOnlineStatus: boolean("show_online_status").notNull().default(true),
|
||||||
|
showReadReceipts: boolean("show_read_receipts").notNull().default(true),
|
||||||
|
|
||||||
// RGPD & Conformité
|
// RGPD & Conformité
|
||||||
termsVersion: varchar("terms_version", { length: 16 }), // Version des CGU acceptées
|
termsVersion: varchar("terms_version", { length: 16 }), // Version des CGU acceptées
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ export class UsersRepository {
|
|||||||
bio: users.bio,
|
bio: users.bio,
|
||||||
status: users.status,
|
status: users.status,
|
||||||
isTwoFactorEnabled: users.isTwoFactorEnabled,
|
isTwoFactorEnabled: users.isTwoFactorEnabled,
|
||||||
|
showOnlineStatus: users.showOnlineStatus,
|
||||||
|
showReadReceipts: users.showReadReceipts,
|
||||||
createdAt: users.createdAt,
|
createdAt: users.createdAt,
|
||||||
updatedAt: users.updatedAt,
|
updatedAt: users.updatedAt,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { forwardRef, Module } from "@nestjs/common";
|
import { forwardRef, Module } from "@nestjs/common";
|
||||||
import { AuthModule } from "../auth/auth.module";
|
import { AuthModule } from "../auth/auth.module";
|
||||||
import { MediaModule } from "../media/media.module";
|
import { MediaModule } from "../media/media.module";
|
||||||
|
import { RealtimeModule } from "../realtime/realtime.module";
|
||||||
import { S3Module } from "../s3/s3.module";
|
import { S3Module } from "../s3/s3.module";
|
||||||
import { UsersRepository } from "./repositories/users.repository";
|
import { UsersRepository } from "./repositories/users.repository";
|
||||||
import { UsersController } from "./users.controller";
|
import { UsersController } from "./users.controller";
|
||||||
import { UsersService } from "./users.service";
|
import { UsersService } from "./users.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [forwardRef(() => AuthModule), MediaModule, S3Module],
|
imports: [
|
||||||
|
forwardRef(() => AuthModule),
|
||||||
|
MediaModule,
|
||||||
|
S3Module,
|
||||||
|
forwardRef(() => RealtimeModule),
|
||||||
|
],
|
||||||
controllers: [UsersController],
|
controllers: [UsersController],
|
||||||
providers: [UsersService, UsersRepository],
|
providers: [UsersService, UsersRepository],
|
||||||
exports: [UsersService, UsersRepository],
|
exports: [UsersService, UsersRepository],
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { ConfigService } from "@nestjs/config";
|
|||||||
import { Test, TestingModule } from "@nestjs/testing";
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
import { RbacService } from "../auth/rbac.service";
|
import { RbacService } from "../auth/rbac.service";
|
||||||
import { MediaService } from "../media/media.service";
|
import { MediaService } from "../media/media.service";
|
||||||
|
import { EventsGateway } from "../realtime/events.gateway";
|
||||||
import { S3Service } from "../s3/s3.service";
|
import { S3Service } from "../s3/s3.service";
|
||||||
import { UsersRepository } from "./repositories/users.repository";
|
import { UsersRepository } from "./repositories/users.repository";
|
||||||
import { UsersService } from "./users.service";
|
import { UsersService } from "./users.service";
|
||||||
@@ -49,6 +50,7 @@ describe("UsersService", () => {
|
|||||||
|
|
||||||
const mockRbacService = {
|
const mockRbacService = {
|
||||||
getUserRoles: jest.fn(),
|
getUserRoles: jest.fn(),
|
||||||
|
assignRoleToUser: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockMediaService = {
|
const mockMediaService = {
|
||||||
@@ -65,6 +67,11 @@ describe("UsersService", () => {
|
|||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockEventsGateway = {
|
||||||
|
isUserOnline: jest.fn(),
|
||||||
|
broadcastStatus: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
@@ -77,6 +84,7 @@ describe("UsersService", () => {
|
|||||||
{ provide: MediaService, useValue: mockMediaService },
|
{ provide: MediaService, useValue: mockMediaService },
|
||||||
{ provide: S3Service, useValue: mockS3Service },
|
{ provide: S3Service, useValue: mockS3Service },
|
||||||
{ provide: ConfigService, useValue: mockConfigService },
|
{ provide: ConfigService, useValue: mockConfigService },
|
||||||
|
{ provide: EventsGateway, useValue: mockEventsGateway },
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { RbacService } from "../auth/rbac.service";
|
|||||||
import type { IMediaService } from "../common/interfaces/media.interface";
|
import type { IMediaService } from "../common/interfaces/media.interface";
|
||||||
import type { IStorageService } from "../common/interfaces/storage.interface";
|
import type { IStorageService } from "../common/interfaces/storage.interface";
|
||||||
import { MediaService } from "../media/media.service";
|
import { MediaService } from "../media/media.service";
|
||||||
|
import { EventsGateway } from "../realtime/events.gateway";
|
||||||
import { S3Service } from "../s3/s3.service";
|
import { S3Service } from "../s3/s3.service";
|
||||||
import { UpdateUserDto } from "./dto/update-user.dto";
|
import { UpdateUserDto } from "./dto/update-user.dto";
|
||||||
import { UsersRepository } from "./repositories/users.repository";
|
import { UsersRepository } from "./repositories/users.repository";
|
||||||
@@ -27,6 +28,8 @@ export class UsersService {
|
|||||||
private readonly rbacService: RbacService,
|
private readonly rbacService: RbacService,
|
||||||
@Inject(MediaService) private readonly mediaService: IMediaService,
|
@Inject(MediaService) private readonly mediaService: IMediaService,
|
||||||
@Inject(S3Service) private readonly s3Service: IStorageService,
|
@Inject(S3Service) private readonly s3Service: IStorageService,
|
||||||
|
@Inject(forwardRef(() => EventsGateway))
|
||||||
|
private readonly eventsGateway: EventsGateway,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async clearUserCache(username?: string) {
|
private async clearUserCache(username?: string) {
|
||||||
@@ -137,6 +140,9 @@ export class UsersService {
|
|||||||
|
|
||||||
const { role, ...userData } = data;
|
const { role, ...userData } = data;
|
||||||
|
|
||||||
|
// On récupère l'utilisateur actuel avant mise à jour pour comparer les préférences
|
||||||
|
const oldUser = await this.usersRepository.findOne(uuid);
|
||||||
|
|
||||||
const result = await this.usersRepository.update(uuid, userData);
|
const result = await this.usersRepository.update(uuid, userData);
|
||||||
|
|
||||||
if (role) {
|
if (role) {
|
||||||
@@ -145,6 +151,21 @@ export class UsersService {
|
|||||||
|
|
||||||
if (result[0]) {
|
if (result[0]) {
|
||||||
await this.clearUserCache(result[0].username);
|
await this.clearUserCache(result[0].username);
|
||||||
|
|
||||||
|
// Gérer le changement de préférence de statut en ligne
|
||||||
|
if (
|
||||||
|
data.showOnlineStatus !== undefined &&
|
||||||
|
data.showOnlineStatus !== oldUser?.showOnlineStatus
|
||||||
|
) {
|
||||||
|
const isOnline = this.eventsGateway.isUserOnline(uuid);
|
||||||
|
if (isOnline) {
|
||||||
|
if (data.showOnlineStatus) {
|
||||||
|
this.eventsGateway.broadcastStatus(uuid, "online");
|
||||||
|
} else {
|
||||||
|
this.eventsGateway.broadcastStatus(uuid, "offline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export interface User {
|
|||||||
role?: "user" | "admin" | "moderator";
|
role?: "user" | "admin" | "moderator";
|
||||||
status?: "active" | "verification" | "suspended" | "pending" | "deleted";
|
status?: "active" | "verification" | "suspended" | "pending" | "deleted";
|
||||||
twoFactorEnabled?: boolean;
|
twoFactorEnabled?: boolean;
|
||||||
|
showOnlineStatus?: boolean;
|
||||||
|
showReadReceipts?: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user