Files
dmenu-api/src/modules/users/user.service.ts
T
2025-11-15 08:41:35 +03:30

141 lines
4.0 KiB
TypeScript

import { Injectable, NotFoundException } from '@nestjs/common';
import { FilterQuery, RequiredEntityData } from '@mikro-orm/core';
import { User } from './entities/user.entity';
import { EntityManager } from '@mikro-orm/postgresql';
import { UpdateUserDto } from './dto/update-user.dto';
import { FindUsersDto } from './dto/find-user.dto';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { UserRepository } from './repositories/user.repository';
@Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly em: EntityManager,
) {}
async findOrCreateByPhone(phone: string): Promise<User> {
let user = await this.userRepository.findOne({ phone });
if (!user) {
// firstName is required on the entity; use phone as a sensible default
const _raw = {
phone,
firstName: phone,
// keep dates undefined (no value) — cast through unknown to satisfy TS typing
birthDate: undefined,
marriageDate: undefined,
wallet: 0,
points: 0,
};
const createData = _raw as unknown as RequiredEntityData<User>;
user = this.userRepository.create(createData);
await this.em.persistAndFlush(user);
}
return user;
}
// async updateUser(userId: string, dto: UpdateUserDto): Promise<User> {
// const user = await this.em.findOneOrFail(User, userId, {}).catch(() => {
// throw new NotFoundException(`User with ID ${userId} not found.`);
// });
// this.em.assign(user, { ...dto });
// await this.em.flush();
// return user;
// }
async findByPhone(phone: string): Promise<User | null> {
return this.userRepository.findOne({ phone });
}
async findById(id: string): Promise<User | null> {
return this.userRepository.findOne({ id });
}
async create(phone: string): Promise<User> {
const _raw = {
phone,
firstName: phone,
birthDate: undefined,
marriageDate: undefined,
wallet: 0,
points: 0,
};
const createData = _raw as unknown as RequiredEntityData<User>;
const user = this.userRepository.create(createData);
await this.em.persistAndFlush(user);
return user;
}
async updateUser(userId: string, dto: UpdateUserDto): Promise<User> {
const user = await this.userRepository.findOne({ id: userId });
if (!user) {
throw new NotFoundException(`User with ID ${userId} not found.`);
}
// Normalize date strings into Date objects if provided
const assignData: Partial<User> = { ...dto } as Partial<User>;
if (dto.birthDate) {
assignData.birthDate = new Date(dto.birthDate);
}
if (dto.marriageDate) {
assignData.marriageDate = new Date(dto.marriageDate);
}
this.em.assign(user, assignData);
await this.em.flush();
return user;
}
async findAll(dto: FindUsersDto): Promise<PaginatedResult<User>> {
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc' } = dto;
// 1. Calculate pagination
const offset = (page - 1) * limit;
// 2. Build the 'where' filter query
const where: FilterQuery<User> = {};
// 4. Add 'search' logic (case-insensitive)
if (search) {
const searchPattern = `%${search}%`;
// $ilike is case-insensitive (PostgreSQL). Use $like for MySQL (often CI by default)
where.$or = [
{ firstName: { $ilike: searchPattern } },
{ lastName: { $ilike: searchPattern } },
{ phone: { $ilike: searchPattern } },
];
}
// 5. Execute the query using findAndCount
const [users, total] = await this.userRepository.findAndCount(where, {
limit,
offset,
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
});
// 6. Calculate total pages
const totalPages = Math.ceil(total / limit);
// 7. Return the paginated result
return {
data: users,
meta: {
total,
page,
limit,
totalPages,
},
};
}
}