forked from rinafcode/teachLink_backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathusers.service.ts
More file actions
171 lines (142 loc) · 5.42 KB
/
Copy pathusers.service.ts
File metadata and controls
171 lines (142 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import * as bcrypt from 'bcryptjs';
import { ensureUserExists, ensureUserDoesNotExist } from '../common/utils/user.utils';
import { paginate, PaginatedResponse } from '../common/utils/pagination.util';
import { PaginationQueryDto } from '../common/dto/pagination.dto';
import { GetUsersDto } from './dto/get-users.dto';
import { CachingService } from '../caching/caching.service';
import { CACHE_TTL, CACHE_PREFIXES, CACHE_EVENTS } from '../caching/caching.constants';
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly cachingService: CachingService,
private readonly eventEmitter: EventEmitter2,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
// Check if user already exists
const existingUser = await this.userRepository.findOne({
where: { email: createUserDto.email },
});
ensureUserDoesNotExist(existingUser, 'User with this email already exists');
// Hash password
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
// Create user
const user = this.userRepository.create({
...createUserDto,
password: hashedPassword,
});
return await this.userRepository.save(user);
}
async findAll(filter?: GetUsersDto): Promise<PaginatedResponse<User>> {
const cacheKey = `cache:users:list:${JSON.stringify(filter || {})}`;
return this.cachingService.getOrSet(
cacheKey,
async () => {
const query = this.userRepository.createQueryBuilder('user');
if (filter?.role) {
query.andWhere('user.role = :role', { role: filter.role });
}
if (filter?.status) {
query.andWhere('user.status = :status', { status: filter.status });
}
if (filter?.search) {
query.andWhere(
'(user.email ILIKE :search OR user.firstName ILIKE :search OR user.lastName ILIKE :search)',
{ search: `%${filter.search}%` },
);
}
return await paginate(query, filter || new PaginationQueryDto());
},
CACHE_TTL.USER_PROFILE,
);
}
async findByIds(ids: string[]): Promise<User[]> {
if (ids.length === 0) return [];
return await this.userRepository.findByIds(ids);
}
/**
* Helper method to find a user by ID or throw NotFoundException.
* Can be used internally to eliminate duplication.
*/
async findUserOrThrow(id: string): Promise<User> {
const user = await this.userRepository.findOne({ where: { id } });
return ensureUserExists(user, 'User not found');
}
async findOne(id: string): Promise<User> {
const cacheKey = `${CACHE_PREFIXES.USER_PROFILE}:${id}`;
return this.cachingService.getOrSet(
cacheKey,
async () => {
return await this.findUserOrThrow(id);
},
CACHE_TTL.USER_PROFILE,
);
}
async findByEmail(email: string): Promise<User | null> {
return await this.userRepository.findOne({ where: { email } });
}
async findByPasswordResetToken(token: string): Promise<User | null> {
return await this.userRepository.findOne({
where: { passwordResetToken: token },
});
}
async findByEmailVerificationToken(token: string): Promise<User | null> {
return await this.userRepository.findOne({
where: { emailVerificationToken: token },
});
}
async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
const user = await this.findUserOrThrow(id);
// If updating password, hash it
if (updateUserDto.password) {
updateUserDto.password = await bcrypt.hash(updateUserDto.password, 10);
}
Object.assign(user, updateUserDto);
const saved = await this.userRepository.save(user);
// Invalidate cache after update
this.eventEmitter.emit(CACHE_EVENTS.USER_UPDATED, { userId: id });
return saved;
}
async updateRefreshToken(userId: string, refreshToken: string | null): Promise<void> {
await this.userRepository.update(userId, { refreshToken: refreshToken as unknown as string });
// Invalidate user cache
this.eventEmitter.emit(CACHE_EVENTS.USER_UPDATED, { userId });
}
async updatePasswordResetToken(
userId: string,
token: string | null,
expires: Date | null,
): Promise<void> {
await this.userRepository.update(userId, {
passwordResetToken: token as unknown as string,
passwordResetExpires: expires as unknown as Date,
});
}
async updateEmailVerificationToken(
userId: string,
token: string | null,
expires: Date | null,
): Promise<void> {
await this.userRepository.update(userId, {
emailVerificationToken: token as unknown as string,
emailVerificationExpires: expires as unknown as Date,
});
}
async updateLastLogin(userId: string): Promise<void> {
await this.userRepository.update(userId, { lastLoginAt: new Date() });
}
async remove(id: string): Promise<void> {
const user = await this.findUserOrThrow(id);
await this.userRepository.remove(user);
// Invalidate cache after delete
this.eventEmitter.emit(CACHE_EVENTS.USER_DELETED, { userId: id });
}
}