346 lines
9.5 KiB
TypeScript
346 lines
9.5 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Player } from 'src/players/entities/player.entity';
|
|
import { EntityCondition } from 'src/utils/types/entity-condition.type';
|
|
import { IPaginationOptions } from 'src/utils/types/pagination-options';
|
|
import { Brackets, Repository, SelectQueryBuilder } from 'typeorm';
|
|
import { RoleEnum } from '../roles/roles.enum';
|
|
import { CreateUserDto } from './dto/create-user.dto';
|
|
import { UpdateUserDto } from './dto/update-user.dto';
|
|
import { UserDirectoryQueryDto } from './dto/user-directory-query.dto';
|
|
import {
|
|
AdminUserDirectorySummaryDto,
|
|
UserDirectoryAssignmentDto,
|
|
UserDirectoryPageDto,
|
|
UserDirectoryReferenceDto,
|
|
UserDirectorySummaryDto,
|
|
} from './dto/user-directory-response.dto';
|
|
import { User } from './entities/user.entity';
|
|
|
|
@Injectable()
|
|
export class UsersService {
|
|
constructor(
|
|
@InjectRepository(User)
|
|
private usersRepository: Repository<User>,
|
|
@InjectRepository(Player)
|
|
private playersRepository: Repository<Player>,
|
|
) {}
|
|
|
|
create(createProfileDto: CreateUserDto) {
|
|
return this.usersRepository.save(
|
|
this.usersRepository.create(createProfileDto),
|
|
);
|
|
}
|
|
|
|
findManyWithPagination(paginationOptions: IPaginationOptions) {
|
|
return this.usersRepository.find({
|
|
skip: (paginationOptions.page - 1) * paginationOptions.limit,
|
|
take: paginationOptions.limit,
|
|
});
|
|
}
|
|
|
|
async findDirectory(
|
|
requester: Pick<User, 'id' | 'role'>,
|
|
query: UserDirectoryQueryDto,
|
|
): Promise<UserDirectoryPageDto> {
|
|
const isAdmin = requester.role?.id === RoleEnum.admin;
|
|
const page = query.page ?? 1;
|
|
const limit = query.limit ?? 20;
|
|
const sharedTeamsQuery = isAdmin
|
|
? undefined
|
|
: this.createSharedTeamsQuery(requester.id);
|
|
const directoryQuery = this.createDirectoryUsersQuery(
|
|
isAdmin,
|
|
sharedTeamsQuery,
|
|
query.search,
|
|
);
|
|
const totalResult = await directoryQuery
|
|
.clone()
|
|
.select('COUNT(DISTINCT user.id)', 'total')
|
|
.getRawOne<{ total: string }>();
|
|
const userRows = await directoryQuery
|
|
.select(this.directoryUserSelects(isAdmin))
|
|
.distinct(true)
|
|
.orderBy('user.id', 'ASC')
|
|
.offset((page - 1) * limit)
|
|
.limit(limit)
|
|
.getRawMany<DirectoryUserRow>();
|
|
const assignments = await this.findDirectoryAssignments(
|
|
userRows.map((user) => Number(user.user_id)),
|
|
isAdmin,
|
|
sharedTeamsQuery,
|
|
);
|
|
const assignmentsByUserId = assignments.reduce((byUserId, assignment) => {
|
|
const userAssignments = byUserId.get(Number(assignment.user_id)) ?? [];
|
|
userAssignments.push(assignment);
|
|
byUserId.set(Number(assignment.user_id), userAssignments);
|
|
return byUserId;
|
|
}, new Map<number, DirectoryAssignmentRow[]>());
|
|
const total = Number(totalResult?.total ?? 0);
|
|
const data = userRows.map((user) =>
|
|
this.mapDirectoryUser(
|
|
user,
|
|
assignmentsByUserId.get(Number(user.user_id)) ?? [],
|
|
isAdmin,
|
|
),
|
|
);
|
|
|
|
return {
|
|
data,
|
|
page,
|
|
limit,
|
|
total,
|
|
hasNextPage: page * limit < total,
|
|
};
|
|
}
|
|
|
|
findOne(fields: EntityCondition<User>) {
|
|
return this.usersRepository.findOne({
|
|
where: fields,
|
|
});
|
|
}
|
|
|
|
update(id: number, updateProfileDto: UpdateUserDto) {
|
|
return this.usersRepository.save(
|
|
this.usersRepository.create({
|
|
id,
|
|
...updateProfileDto,
|
|
}),
|
|
);
|
|
}
|
|
|
|
async softDelete(id: number): Promise<void> {
|
|
await this.usersRepository.softDelete(id);
|
|
}
|
|
|
|
async findTeams(fields: EntityCondition<User>) {
|
|
const user = await this.findOne(fields);
|
|
if (!user) {
|
|
return [];
|
|
}
|
|
|
|
const players = await this.playersRepository.find({
|
|
where: {
|
|
user: {
|
|
id: user.id,
|
|
},
|
|
},
|
|
relations: ['team'],
|
|
});
|
|
return players;
|
|
}
|
|
|
|
async linkPlayerToUserId(user: User, playerId: number): Promise<boolean> {
|
|
return new Promise<boolean>(async (resolve) => {
|
|
const player = await this.playersRepository.findOneByOrFail({
|
|
id: playerId,
|
|
});
|
|
|
|
player.user = user;
|
|
await this.playersRepository.save(player);
|
|
return resolve(true);
|
|
});
|
|
}
|
|
|
|
private createSharedTeamsQuery(requesterId: number) {
|
|
return this.playersRepository
|
|
.createQueryBuilder('requesterPlayer')
|
|
.select('requesterPlayer.teamId')
|
|
.where('requesterPlayer.userId = :requesterId', { requesterId });
|
|
}
|
|
|
|
private createDirectoryUsersQuery(
|
|
isAdmin: boolean,
|
|
sharedTeamsQuery: SelectQueryBuilder<Player> | undefined,
|
|
search: string | undefined,
|
|
) {
|
|
const query = this.usersRepository
|
|
.createQueryBuilder('user')
|
|
.leftJoin('user.status', 'status')
|
|
.leftJoin('user.role', 'role');
|
|
|
|
if (!isAdmin && sharedTeamsQuery) {
|
|
query
|
|
.innerJoin(
|
|
'user.players',
|
|
'visibleAssignment',
|
|
`visibleAssignment.teamId IN (${sharedTeamsQuery.getQuery()})`,
|
|
)
|
|
.setParameters(sharedTeamsQuery.getParameters());
|
|
}
|
|
|
|
const term = search?.trim().toLocaleLowerCase();
|
|
if (!term) {
|
|
return query;
|
|
}
|
|
|
|
const assignmentAlias = isAdmin ? 'searchAssignment' : 'visibleAssignment';
|
|
if (isAdmin) {
|
|
query.leftJoin('user.players', assignmentAlias);
|
|
}
|
|
|
|
const searchColumns = [
|
|
'user.firstName',
|
|
'user.lastName',
|
|
`${assignmentAlias}.firstName`,
|
|
`${assignmentAlias}.lastName`,
|
|
...(isAdmin ? ['user.email'] : []),
|
|
];
|
|
query.andWhere(
|
|
new Brackets((where) => {
|
|
searchColumns.forEach((column, index) => {
|
|
const expression = `LOWER(${column}) LIKE :directorySearch`;
|
|
if (index === 0) {
|
|
where.where(expression);
|
|
} else {
|
|
where.orWhere(expression);
|
|
}
|
|
});
|
|
}),
|
|
);
|
|
return query.setParameter('directorySearch', `%${term}%`);
|
|
}
|
|
|
|
private directoryUserSelects(isAdmin: boolean): string[] {
|
|
return [
|
|
'user.id AS user_id',
|
|
'user.firstName AS first_name',
|
|
'user.lastName AS last_name',
|
|
'status.id AS status_id',
|
|
'status.name AS status_name',
|
|
...(isAdmin
|
|
? [
|
|
'user.email AS email',
|
|
'role.id AS role_id',
|
|
'role.name AS role_name',
|
|
]
|
|
: []),
|
|
];
|
|
}
|
|
|
|
private async findDirectoryAssignments(
|
|
userIds: number[],
|
|
isAdmin: boolean,
|
|
sharedTeamsQuery: SelectQueryBuilder<Player> | undefined,
|
|
): Promise<DirectoryAssignmentRow[]> {
|
|
if (userIds.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const query = this.playersRepository
|
|
.createQueryBuilder('player')
|
|
.innerJoin('player.team', 'team')
|
|
.leftJoin('player.teamRole', 'teamRole')
|
|
.select([
|
|
'player.id AS player_id',
|
|
'player.userId AS user_id',
|
|
'player.firstName AS first_name',
|
|
'player.lastName AS last_name',
|
|
'player.active AS active',
|
|
'team.id AS team_id',
|
|
'team.name AS team_name',
|
|
'team.alias AS team_alias',
|
|
'teamRole.id AS team_role_id',
|
|
'teamRole.name AS team_role_name',
|
|
])
|
|
.where('player.userId IN (:...userIds)', { userIds })
|
|
.orderBy('player.id', 'ASC');
|
|
|
|
if (!isAdmin && sharedTeamsQuery) {
|
|
query
|
|
.andWhere(`player.teamId IN (${sharedTeamsQuery.getQuery()})`)
|
|
.setParameters(sharedTeamsQuery.getParameters());
|
|
}
|
|
|
|
return query.getRawMany<DirectoryAssignmentRow>();
|
|
}
|
|
|
|
private mapDirectoryUser(
|
|
user: DirectoryUserRow,
|
|
assignments: DirectoryAssignmentRow[],
|
|
includeAdminFields: boolean,
|
|
): UserDirectorySummaryDto | AdminUserDirectorySummaryDto {
|
|
const summary: UserDirectorySummaryDto = {
|
|
id: Number(user.user_id),
|
|
firstName: user.first_name,
|
|
lastName: user.last_name,
|
|
status: this.mapDirectoryReference(user.status_id, user.status_name),
|
|
assignments: assignments.map((assignment) =>
|
|
this.mapDirectoryAssignment(assignment),
|
|
),
|
|
};
|
|
|
|
if (!includeAdminFields) {
|
|
return summary;
|
|
}
|
|
|
|
return {
|
|
...summary,
|
|
email: user.email,
|
|
role: this.mapDirectoryReference(user.role_id, user.role_name),
|
|
};
|
|
}
|
|
|
|
private mapDirectoryAssignment(
|
|
player: DirectoryAssignmentRow,
|
|
): UserDirectoryAssignmentDto {
|
|
return {
|
|
id: Number(player.player_id),
|
|
firstName: player.first_name,
|
|
lastName: player.last_name,
|
|
active: this.asBoolean(player.active),
|
|
team: {
|
|
id: Number(player.team_id),
|
|
name: player.team_name,
|
|
alias: player.team_alias,
|
|
},
|
|
teamRole: this.mapDirectoryReference(
|
|
player.team_role_id,
|
|
player.team_role_name,
|
|
),
|
|
};
|
|
}
|
|
|
|
private asBoolean(value: boolean | number | string): boolean {
|
|
return value === true || value === 1 || value === '1';
|
|
}
|
|
|
|
private mapDirectoryReference(
|
|
id: number | string | null,
|
|
name: string | null,
|
|
): UserDirectoryReferenceDto | null {
|
|
if (id == null) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: Number(id),
|
|
name: name ?? undefined,
|
|
};
|
|
}
|
|
}
|
|
|
|
interface DirectoryUserRow {
|
|
user_id: number | string;
|
|
first_name: string | null;
|
|
last_name: string | null;
|
|
status_id: number | string | null;
|
|
status_name: string | null;
|
|
email?: string | null;
|
|
role_id?: number | string | null;
|
|
role_name?: string | null;
|
|
}
|
|
|
|
interface DirectoryAssignmentRow {
|
|
player_id: number | string;
|
|
user_id: number | string;
|
|
first_name: string;
|
|
last_name: string;
|
|
active: boolean | number | string;
|
|
team_id: number | string;
|
|
team_name: string;
|
|
team_alias: string;
|
|
team_role_id: number | string | null;
|
|
team_role_name: string | null;
|
|
}
|