refactor(server): user core (#4722)
This commit is contained in:
@@ -1,17 +1,9 @@
|
||||
import { LibraryType, UserEntity } from '@app/infra/entities';
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { ReadStream, constants, createReadStream } from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import { BadRequestException, ForbiddenException, InternalServerErrorException, Logger } from '@nestjs/common';
|
||||
import path from 'path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { ICryptoRepository, ILibraryRepository, IUserRepository, UserListFilter } from '../repositories';
|
||||
import { ICryptoRepository, ILibraryRepository, IUserRepository } from '../repositories';
|
||||
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
@@ -131,34 +123,6 @@ export class UserCore {
|
||||
}
|
||||
}
|
||||
|
||||
async get(userId: string, withDeleted?: boolean): Promise<UserEntity | null> {
|
||||
return this.userRepository.get(userId, withDeleted);
|
||||
}
|
||||
|
||||
async getAdmin(): Promise<UserEntity | null> {
|
||||
return this.userRepository.getAdmin();
|
||||
}
|
||||
|
||||
async getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | null> {
|
||||
return this.userRepository.getByEmail(email, withPassword);
|
||||
}
|
||||
|
||||
async getByOAuthId(oauthId: string): Promise<UserEntity | null> {
|
||||
return this.userRepository.getByOAuthId(oauthId);
|
||||
}
|
||||
|
||||
async getUserProfileImage(user: UserEntity): Promise<ReadStream> {
|
||||
if (!user.profileImagePath) {
|
||||
throw new NotFoundException('User does not have a profile image');
|
||||
}
|
||||
await fs.access(user.profileImagePath, constants.R_OK);
|
||||
return createReadStream(user.profileImagePath);
|
||||
}
|
||||
|
||||
async getList(filter?: UserListFilter): Promise<UserEntity[]> {
|
||||
return this.userRepository.getList(filter);
|
||||
}
|
||||
|
||||
async createProfileImage(authUser: AuthUserDto, filePath: string): Promise<UserEntity> {
|
||||
try {
|
||||
return this.userRepository.update(authUser.id, { profileImagePath: filePath });
|
||||
|
||||
@@ -149,9 +149,7 @@ describe(UserService.name, () => {
|
||||
sut = new UserService(albumMock, assetMock, cryptoRepositoryMock, jobMock, libraryMock, storageMock, userMock);
|
||||
|
||||
when(userMock.get).calledWith(adminUser.id).mockResolvedValue(adminUser);
|
||||
when(userMock.get).calledWith(adminUser.id, undefined).mockResolvedValue(adminUser);
|
||||
when(userMock.get).calledWith(immichUser.id).mockResolvedValue(immichUser);
|
||||
when(userMock.get).calledWith(immichUser.id, undefined).mockResolvedValue(immichUser);
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
@@ -207,7 +205,7 @@ describe(UserService.name, () => {
|
||||
|
||||
const response = await sut.getMe(adminUser);
|
||||
|
||||
expect(userMock.get).toHaveBeenCalledWith(adminUser.id, undefined);
|
||||
expect(userMock.get).toHaveBeenCalledWith(adminUser.id);
|
||||
expect(response).toEqual(adminUserResponse);
|
||||
});
|
||||
|
||||
@@ -216,7 +214,7 @@ describe(UserService.name, () => {
|
||||
|
||||
await expect(sut.getMe(adminUser)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(userMock.get).toHaveBeenCalledWith(adminUser.id, undefined);
|
||||
expect(userMock.get).toHaveBeenCalledWith(adminUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -256,7 +254,7 @@ describe(UserService.name, () => {
|
||||
|
||||
it('user can only update its information', async () => {
|
||||
when(userMock.get)
|
||||
.calledWith('not_immich_auth_user_id', undefined)
|
||||
.calledWith('not_immich_auth_user_id')
|
||||
.mockResolvedValueOnce({
|
||||
...immichUser,
|
||||
id: 'not_immich_auth_user_id',
|
||||
@@ -321,7 +319,7 @@ describe(UserService.name, () => {
|
||||
});
|
||||
|
||||
it('update user information should throw error if user not found', async () => {
|
||||
when(userMock.get).calledWith(immichUser.id, undefined).mockResolvedValueOnce(null);
|
||||
when(userMock.get).calledWith(immichUser.id).mockResolvedValueOnce(null);
|
||||
|
||||
const result = sut.update(adminUser, {
|
||||
id: immichUser.id,
|
||||
@@ -334,7 +332,6 @@ describe(UserService.name, () => {
|
||||
it('should let the admin update himself', async () => {
|
||||
const dto = { id: adminUser.id, shouldChangePassword: true, isAdmin: true };
|
||||
|
||||
when(userMock.get).calledWith(adminUser.id).mockResolvedValueOnce(null);
|
||||
when(userMock.update).calledWith(adminUser.id, dto).mockResolvedValueOnce(adminUser);
|
||||
|
||||
await sut.update(adminUser, dto);
|
||||
@@ -398,7 +395,7 @@ describe(UserService.name, () => {
|
||||
userMock.delete.mockResolvedValue(immichUser);
|
||||
|
||||
await expect(sut.delete(adminUserAuth, immichUser.id)).resolves.toEqual(mapUser(immichUser));
|
||||
expect(userMock.get).toHaveBeenCalledWith(immichUser.id, undefined);
|
||||
expect(userMock.get).toHaveBeenCalledWith(immichUser.id);
|
||||
expect(userMock.delete).toHaveBeenCalledWith(immichUser);
|
||||
});
|
||||
});
|
||||
@@ -466,7 +463,7 @@ describe(UserService.name, () => {
|
||||
|
||||
await expect(sut.getProfileImage(adminUserAuth.id)).rejects.toBeInstanceOf(NotFoundException);
|
||||
|
||||
expect(userMock.get).toHaveBeenCalledWith(adminUserAuth.id, undefined);
|
||||
expect(userMock.get).toHaveBeenCalledWith(adminUserAuth.id);
|
||||
});
|
||||
|
||||
it('should throw an error if the user does not have a picture', async () => {
|
||||
@@ -474,7 +471,7 @@ describe(UserService.name, () => {
|
||||
|
||||
await expect(sut.getProfileImage(adminUserAuth.id)).rejects.toBeInstanceOf(NotFoundException);
|
||||
|
||||
expect(userMock.get).toHaveBeenCalledWith(adminUserAuth.id, undefined);
|
||||
expect(userMock.get).toHaveBeenCalledWith(adminUserAuth.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { UserEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { ReadStream } from 'fs';
|
||||
import { ReadStream, constants, createReadStream } from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { IEntityJob, JobName } from '../job';
|
||||
import {
|
||||
@@ -36,12 +37,12 @@ export class UserService {
|
||||
}
|
||||
|
||||
async getAll(authUser: AuthUserDto, isAll: boolean): Promise<UserResponseDto[]> {
|
||||
const users = await this.userCore.getList({ withDeleted: !isAll });
|
||||
const users = await this.userRepository.getList({ withDeleted: !isAll });
|
||||
return users.map(mapUser);
|
||||
}
|
||||
|
||||
async get(userId: string, withDeleted = false): Promise<UserResponseDto> {
|
||||
const user = await this.userCore.get(userId, withDeleted);
|
||||
const user = await this.userRepository.get(userId, withDeleted);
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
@@ -50,7 +51,7 @@ export class UserService {
|
||||
}
|
||||
|
||||
async getMe(authUser: AuthUserDto): Promise<UserResponseDto> {
|
||||
const user = await this.userCore.get(authUser.id);
|
||||
const user = await this.userRepository.get(authUser.id);
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
@@ -63,7 +64,7 @@ export class UserService {
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, dto: UpdateUserDto): Promise<UserResponseDto> {
|
||||
const user = await this.userCore.get(dto.id);
|
||||
const user = await this.userRepository.get(dto.id);
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
@@ -73,7 +74,7 @@ export class UserService {
|
||||
}
|
||||
|
||||
async delete(authUser: AuthUserDto, userId: string): Promise<UserResponseDto> {
|
||||
const user = await this.userCore.get(userId);
|
||||
const user = await this.userRepository.get(userId);
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
@@ -83,7 +84,7 @@ export class UserService {
|
||||
}
|
||||
|
||||
async restore(authUser: AuthUserDto, userId: string): Promise<UserResponseDto> {
|
||||
const user = await this.userCore.get(userId, true);
|
||||
const user = await this.userRepository.get(userId, true);
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
@@ -101,15 +102,19 @@ export class UserService {
|
||||
}
|
||||
|
||||
async getProfileImage(userId: string): Promise<ReadStream> {
|
||||
const user = await this.userCore.get(userId);
|
||||
const user = await this.userRepository.get(userId);
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
return this.userCore.getUserProfileImage(user);
|
||||
if (!user.profileImagePath) {
|
||||
throw new NotFoundException('User does not have a profile image');
|
||||
}
|
||||
await fs.access(user.profileImagePath, constants.R_OK);
|
||||
return createReadStream(user.profileImagePath);
|
||||
}
|
||||
|
||||
async resetAdminPassword(ask: (admin: UserResponseDto) => Promise<string | undefined>) {
|
||||
const admin = await this.userCore.getAdmin();
|
||||
const admin = await this.userRepository.getAdmin();
|
||||
if (!admin) {
|
||||
throw new BadRequestException('Admin account does not exist');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user