mirror of
https://github.com/immich-app/immich.git
synced 2025-12-27 09:14:55 +03:00
refactor: remove user entity (#17498)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
import { UserAdmin } from 'src/database';
|
||||
import { AuthDto, SignUpDto } from 'src/dtos/auth.dto';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { AuthType, Permission } from 'src/enum';
|
||||
import { AuthService } from 'src/services/auth.service';
|
||||
import { UserMetadataItem } from 'src/types';
|
||||
@@ -89,7 +89,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should check the user has a password', async () => {
|
||||
mocks.user.getByEmail.mockResolvedValue({} as UserEntity);
|
||||
mocks.user.getByEmail.mockResolvedValue({} as UserAdmin);
|
||||
|
||||
await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException);
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should successfully log the user in', async () => {
|
||||
const user = { ...factory.user(), password: 'immich_password' } as UserEntity;
|
||||
const user = { ...(factory.user() as UserAdmin), password: 'immich_password' };
|
||||
const session = factory.session();
|
||||
mocks.user.getByEmail.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(session);
|
||||
@@ -124,7 +124,7 @@ describe('AuthService', () => {
|
||||
mocks.user.getByEmail.mockResolvedValue({
|
||||
email: 'test@immich.com',
|
||||
password: 'hash-password',
|
||||
} as UserEntity);
|
||||
} as UserAdmin & { password: string });
|
||||
mocks.user.update.mockResolvedValue(userStub.user1);
|
||||
|
||||
await sut.changePassword(auth, dto);
|
||||
@@ -143,7 +143,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should throw when password does not match existing password', async () => {
|
||||
const auth = { user: { email: 'test@imimch.com' } as UserEntity };
|
||||
const auth = { user: { email: 'test@imimch.com' } as UserAdmin };
|
||||
const dto = { password: 'old-password', newPassword: 'new-password' };
|
||||
|
||||
mocks.crypto.compareBcrypt.mockReturnValue(false);
|
||||
@@ -151,7 +151,7 @@ describe('AuthService', () => {
|
||||
mocks.user.getByEmail.mockResolvedValue({
|
||||
email: 'test@immich.com',
|
||||
password: 'hash-password',
|
||||
} as UserEntity);
|
||||
} as UserAdmin & { password: string });
|
||||
|
||||
await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
@@ -163,7 +163,7 @@ describe('AuthService', () => {
|
||||
mocks.user.getByEmail.mockResolvedValue({
|
||||
email: 'test@immich.com',
|
||||
password: '',
|
||||
} as UserEntity);
|
||||
} as UserAdmin & { password: string });
|
||||
|
||||
await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
@@ -217,7 +217,7 @@ describe('AuthService', () => {
|
||||
const dto: SignUpDto = { email: 'test@immich.com', password: 'password', name: 'immich admin' };
|
||||
|
||||
it('should only allow one admin', async () => {
|
||||
mocks.user.getAdmin.mockResolvedValue({} as UserEntity);
|
||||
mocks.user.getAdmin.mockResolvedValue({} as UserAdmin);
|
||||
|
||||
await expect(sut.adminSignUp(dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
@@ -231,7 +231,7 @@ describe('AuthService', () => {
|
||||
id: 'admin',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
metadata: [] as UserMetadataItem[],
|
||||
} as UserEntity);
|
||||
} as unknown as UserAdmin);
|
||||
|
||||
await expect(sut.adminSignUp(dto)).resolves.toMatchObject({
|
||||
avatarColor: expect.any(String),
|
||||
@@ -294,7 +294,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should not accept an expired key', async () => {
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired as any);
|
||||
|
||||
await expect(
|
||||
sut.authenticate({
|
||||
@@ -306,7 +306,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should not accept a key on a non-shared route', async () => {
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid);
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid as any);
|
||||
|
||||
await expect(
|
||||
sut.authenticate({
|
||||
@@ -318,7 +318,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should not accept a key without a user', async () => {
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired as any);
|
||||
mocks.user.get.mockResolvedValue(void 0);
|
||||
|
||||
await expect(
|
||||
@@ -331,7 +331,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should accept a base64url key', async () => {
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid);
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid as any);
|
||||
mocks.user.get.mockResolvedValue(userStub.admin);
|
||||
|
||||
await expect(
|
||||
@@ -348,7 +348,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should accept a hex key', async () => {
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid);
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid as any);
|
||||
mocks.user.get.mockResolvedValue(userStub.admin);
|
||||
|
||||
await expect(
|
||||
@@ -717,7 +717,7 @@ describe('AuthService', () => {
|
||||
const auth = { user: authUser, apiKey: authApiKey };
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled);
|
||||
mocks.user.getByOAuthId.mockResolvedValue({ id: 'other-user' } as UserEntity);
|
||||
mocks.user.getByOAuthId.mockResolvedValue({ id: 'other-user' } as UserAdmin);
|
||||
|
||||
await expect(sut.link(auth, { url: 'http://immich/user-settings?code=abc123' })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { parse } from 'cookie';
|
||||
import { DateTime } from 'luxon';
|
||||
import { IncomingHttpHeaders } from 'node:http';
|
||||
import { LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
|
||||
import { UserAdmin } from 'src/database';
|
||||
import { OnEvent } from 'src/decorators';
|
||||
import {
|
||||
AuthDto,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
mapLoginResponse,
|
||||
} from 'src/dtos/auth.dto';
|
||||
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum';
|
||||
import { OAuthProfile } from 'src/repositories/oauth.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
@@ -190,7 +190,7 @@ export class AuthService extends BaseService {
|
||||
const profile = await this.oauthRepository.getProfile(oauth, dto.url, this.resolveRedirectUri(oauth, dto.url));
|
||||
const { autoRegister, defaultStorageQuota, storageLabelClaim, storageQuotaClaim } = oauth;
|
||||
this.logger.debug(`Logging in with OAuth: ${JSON.stringify(profile)}`);
|
||||
let user = await this.userRepository.getByOAuthId(profile.sub);
|
||||
let user: UserAdmin | undefined = await this.userRepository.getByOAuthId(profile.sub);
|
||||
|
||||
// link by email
|
||||
if (!user && profile.email) {
|
||||
@@ -318,7 +318,7 @@ export class AuthService extends BaseService {
|
||||
throw new UnauthorizedException('Invalid API key');
|
||||
}
|
||||
|
||||
private validatePassword(inputPassword: string, user: UserEntity): boolean {
|
||||
private validatePassword(inputPassword: string, user: { password?: string }): boolean {
|
||||
if (!user || !user.password) {
|
||||
return false;
|
||||
}
|
||||
@@ -347,7 +347,7 @@ export class AuthService extends BaseService {
|
||||
throw new UnauthorizedException('Invalid user token');
|
||||
}
|
||||
|
||||
private async createLoginResponse(user: UserEntity, loginDetails: LoginDetails) {
|
||||
private async createLoginResponse(user: UserAdmin, loginDetails: LoginDetails) {
|
||||
const key = this.cryptoRepository.newPassword(32);
|
||||
const token = this.cryptoRepository.hashSha256(key);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import sanitize from 'sanitize-filename';
|
||||
import { SystemConfig } from 'src/config';
|
||||
import { SALT_ROUNDS } from 'src/constants';
|
||||
import { StorageCore } from 'src/cores/storage.core';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { UserAdmin } from 'src/database';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
@@ -138,7 +138,7 @@ export class BaseService {
|
||||
return checkAccess(this.accessRepository, request);
|
||||
}
|
||||
|
||||
async createUser(dto: Insertable<UserTable> & { email: string }): Promise<UserEntity> {
|
||||
async createUser(dto: Insertable<UserTable> & { email: string }): Promise<UserAdmin> {
|
||||
const user = await this.userRepository.getByEmail(dto.email);
|
||||
if (user) {
|
||||
throw new BadRequestException('User exists');
|
||||
|
||||
@@ -13,6 +13,7 @@ import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { faceStub } from 'test/fixtures/face.stub';
|
||||
import { personStub } from 'test/fixtures/person.stub';
|
||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
|
||||
|
||||
const responseDto: PersonResponseDto = {
|
||||
@@ -1279,7 +1280,8 @@ describe(PersonService.name, () => {
|
||||
|
||||
describe('mapFace', () => {
|
||||
it('should map a face', () => {
|
||||
expect(mapFaces(faceStub.face1, { user: personStub.withName.owner })).toEqual({
|
||||
const authDto = factory.auth({ id: faceStub.face1.person.ownerId });
|
||||
expect(mapFaces(faceStub.face1, authDto)).toEqual({
|
||||
boundingBoxX1: 0,
|
||||
boundingBoxX2: 1,
|
||||
boundingBoxY1: 0,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BadRequestException, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { UserAdmin } from 'src/database';
|
||||
import { CacheControl, JobName, UserMetadataKey } from 'src/enum';
|
||||
import { UserService } from 'src/services/user.service';
|
||||
import { ImmichFileResponse } from 'src/utils/file';
|
||||
@@ -214,7 +214,7 @@ describe(UserService.name, () => {
|
||||
|
||||
describe('handleUserDelete', () => {
|
||||
it('should skip users not ready for deletion', async () => {
|
||||
const user = { id: 'user-1', deletedAt: makeDeletedAt(5) } as UserEntity;
|
||||
const user = { id: 'user-1', deletedAt: makeDeletedAt(5) } as UserAdmin;
|
||||
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
|
||||
@@ -225,7 +225,7 @@ describe(UserService.name, () => {
|
||||
});
|
||||
|
||||
it('should delete the user and associated assets', async () => {
|
||||
const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10) } as UserEntity;
|
||||
const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10) } as UserAdmin;
|
||||
const options = { force: true, recursive: true };
|
||||
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
@@ -242,7 +242,7 @@ describe(UserService.name, () => {
|
||||
});
|
||||
|
||||
it('should delete the library path for a storage label', async () => {
|
||||
const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10), storageLabel: 'admin' } as UserEntity;
|
||||
const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10), storageLabel: 'admin' } as UserAdmin;
|
||||
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Updateable } from 'kysely';
|
||||
import { DateTime } from 'luxon';
|
||||
import { SALT_ROUNDS } from 'src/constants';
|
||||
import { StorageCore } from 'src/cores/storage.core';
|
||||
@@ -8,9 +9,9 @@ import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
|
||||
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
|
||||
import { CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
|
||||
import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUserAdmin } from 'src/dtos/user.dto';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { CacheControl, JobName, JobStatus, QueueName, StorageFolder, UserMetadataKey } from 'src/enum';
|
||||
import { UserFindOptions } from 'src/repositories/user.repository';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { JobOf, UserMetadataItem } from 'src/types';
|
||||
import { ImmichFileResponse } from 'src/utils/file';
|
||||
@@ -49,7 +50,7 @@ export class UserService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
const update: Partial<UserEntity> = {
|
||||
const update: Updateable<UserTable> = {
|
||||
email: dto.email,
|
||||
name: dto.name,
|
||||
};
|
||||
@@ -229,7 +230,7 @@ export class UserService extends BaseService {
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
private isReadyForDeletion(user: UserEntity, deleteDelay: number): boolean {
|
||||
private isReadyForDeletion(user: { id: string; deletedAt?: Date | null }, deleteDelay: number): boolean {
|
||||
if (!user.deletedAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user