258 lines
6.5 KiB
TypeScript
258 lines
6.5 KiB
TypeScript
import { Inject, Injectable } from '@nestjs/common';
|
|
import { ILike, Repository } from 'typeorm';
|
|
|
|
import { UserTokenType } from '../user-token/user-token.entity';
|
|
import { User } from './user.entity';
|
|
import { TokenService } from 'src/modules/utility/services/token.service';
|
|
import { EmailService } from '../email/email.service';
|
|
import { RegistrationEmail } from './email/registration.email';
|
|
import { ForgotPasswordEmail } from './email/forgot-password.email';
|
|
import { UserTokenService } from '../user-token/user-token.service';
|
|
import { ConfigurationService } from 'src/modules/config/config.service';
|
|
import { Upload } from '../upload/upload.entity';
|
|
import { UploadService } from '../upload/upload.service';
|
|
|
|
@Injectable()
|
|
export class UserService {
|
|
constructor(
|
|
@Inject('USER_REPOSITORY')
|
|
private userRepository: Repository<User>,
|
|
private userToken: UserTokenService,
|
|
private token: TokenService,
|
|
private email: EmailService,
|
|
private config: ConfigurationService,
|
|
private upload: UploadService,
|
|
) {}
|
|
|
|
public async getById(id: number, relations?: string[]): Promise<User> {
|
|
if (!id) {
|
|
return null;
|
|
}
|
|
return this.userRepository.findOne({ where: { id }, relations });
|
|
}
|
|
|
|
public async getByUUID(uuid: string, relations?: string[]): Promise<User> {
|
|
if (!uuid) {
|
|
return null;
|
|
}
|
|
|
|
return this.userRepository.findOne({ where: { uuid }, relations });
|
|
}
|
|
|
|
public async getByEmail(email: string, relations?: string[]): Promise<User> {
|
|
if (!email) {
|
|
return null;
|
|
}
|
|
|
|
return this.userRepository.findOne({ where: { email }, relations });
|
|
}
|
|
|
|
public async searchUsers(
|
|
limit = 50,
|
|
offset = 0,
|
|
search?: string,
|
|
relations?: string[],
|
|
): Promise<[User[], number]> {
|
|
const searchTerm = `%${search}%`;
|
|
return this.userRepository.findAndCount({
|
|
where: search
|
|
? [
|
|
{
|
|
display_name: ILike(searchTerm),
|
|
},
|
|
{
|
|
username: ILike(searchTerm),
|
|
},
|
|
{
|
|
email: ILike(searchTerm),
|
|
},
|
|
]
|
|
: undefined,
|
|
skip: offset,
|
|
take: limit,
|
|
relations,
|
|
});
|
|
}
|
|
|
|
public async searchUsersCount(
|
|
search?: string,
|
|
relations?: string[],
|
|
): Promise<number> {
|
|
const searchTerm = `%${search}%`;
|
|
return this.userRepository.count({
|
|
where: search
|
|
? [
|
|
{
|
|
display_name: ILike(searchTerm),
|
|
},
|
|
{
|
|
username: ILike(searchTerm),
|
|
},
|
|
{
|
|
email: ILike(searchTerm),
|
|
},
|
|
]
|
|
: undefined,
|
|
relations,
|
|
});
|
|
}
|
|
|
|
public async getByUsername(
|
|
username: string,
|
|
relations?: string[],
|
|
): Promise<User> {
|
|
if (!username) {
|
|
return null;
|
|
}
|
|
|
|
return this.userRepository.findOne({ where: { username }, relations });
|
|
}
|
|
|
|
public async get(
|
|
input: string | number,
|
|
relations?: string[],
|
|
): Promise<User> {
|
|
if (typeof input === 'number') {
|
|
return this.getById(input, relations);
|
|
}
|
|
|
|
if (input.includes('@')) {
|
|
return this.getByEmail(input, relations);
|
|
}
|
|
|
|
if (input.length === 36 && input.includes('-')) {
|
|
return this.getByUUID(input, relations);
|
|
}
|
|
|
|
return this.getByUsername(input, relations);
|
|
}
|
|
|
|
public async updateUser(user: User): Promise<User> {
|
|
await this.userRepository.save(user);
|
|
return user;
|
|
}
|
|
|
|
public async updateAvatar(user: User, upload: Upload): Promise<User> {
|
|
if (user.picture) {
|
|
await this.upload.delete(user.picture);
|
|
}
|
|
|
|
user.picture = upload;
|
|
await this.updateUser(user);
|
|
return user;
|
|
}
|
|
|
|
public async deleteAvatar(user: User): Promise<void> {
|
|
if (user.picture) {
|
|
await this.upload.delete(user.picture);
|
|
}
|
|
}
|
|
|
|
public async sendActivationEmail(
|
|
user: User,
|
|
redirectTo?: string,
|
|
): Promise<void> {
|
|
const activationToken = await this.userToken.create(
|
|
user,
|
|
UserTokenType.ACTIVATION,
|
|
new Date(Date.now() + 3600 * 1000),
|
|
);
|
|
|
|
const params = new URLSearchParams({ token: activationToken.token });
|
|
if (redirectTo) {
|
|
params.append('redirectTo', redirectTo);
|
|
}
|
|
|
|
try {
|
|
const content = RegistrationEmail(
|
|
user.username,
|
|
`${this.config.get<string>(
|
|
'app.base_url',
|
|
)}/login/activate?${params.toString()}`,
|
|
);
|
|
await this.email.sendEmailTemplate(
|
|
user.email,
|
|
'Activate your account on Icy Network',
|
|
content,
|
|
);
|
|
} catch (e) {
|
|
await this.userToken.delete(activationToken);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
public async sendPasswordEmail(user: User): Promise<void> {
|
|
const passwordToken = await this.userToken.create(
|
|
user,
|
|
UserTokenType.PASSWORD,
|
|
new Date(Date.now() + 3600 * 1000),
|
|
);
|
|
|
|
try {
|
|
const content = ForgotPasswordEmail(
|
|
user.username,
|
|
`${this.config.get<string>('app.base_url')}/login/password?token=${
|
|
passwordToken.token
|
|
}`,
|
|
);
|
|
await this.email.sendEmailTemplate(
|
|
user.email,
|
|
'Reset your password on Icy Network',
|
|
content,
|
|
);
|
|
} catch (e) {
|
|
await this.userToken.delete(passwordToken);
|
|
// silently fail
|
|
}
|
|
}
|
|
|
|
public async userPassword(email: string): Promise<void> {
|
|
const user = await this.getByEmail(email);
|
|
if (!user || !user.activated) {
|
|
return;
|
|
}
|
|
|
|
await this.sendPasswordEmail(user);
|
|
}
|
|
|
|
public async userRegistration(
|
|
newUserInfo: {
|
|
username: string;
|
|
display_name: string;
|
|
email: string;
|
|
password: string;
|
|
},
|
|
redirectTo?: string,
|
|
): Promise<User> {
|
|
if (!!(await this.getByEmail(newUserInfo.email))) {
|
|
throw new Error('Email is already in use!');
|
|
}
|
|
|
|
if (!!(await this.getByUsername(newUserInfo.username))) {
|
|
throw new Error('Username is already in use!');
|
|
}
|
|
|
|
const hashword = await this.token.hashPassword(newUserInfo.password);
|
|
const user = new User();
|
|
user.email = newUserInfo.email;
|
|
user.uuid = this.token.createUUID();
|
|
user.username = newUserInfo.username;
|
|
user.display_name = newUserInfo.display_name;
|
|
user.password = hashword;
|
|
user.activity_at = new Date();
|
|
|
|
await this.userRepository.insert(user);
|
|
|
|
try {
|
|
await this.sendActivationEmail(user, redirectTo);
|
|
} catch (e) {
|
|
await this.userRepository.remove(user);
|
|
throw new Error(
|
|
'Failed to send activation email! Please check your email address and try again!',
|
|
);
|
|
}
|
|
|
|
return user;
|
|
}
|
|
}
|