2023-09-20 13:16:33 +02:00
|
|
|
import { LibraryType, UserEntity } from '@app/infra/entities';
|
2023-11-03 21:33:15 -04:00
|
|
|
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
2023-07-15 15:50:29 -04:00
|
|
|
import sanitize from 'sanitize-filename';
|
2023-10-30 17:02:36 -04:00
|
|
|
import { ICryptoRepository, ILibraryRepository, IUserRepository } from '../repositories';
|
2023-12-09 23:34:12 -05:00
|
|
|
import { UserResponseDto } from './response-dto';
|
2022-12-23 21:08:50 +01:00
|
|
|
|
2022-12-26 23:03:14 -05:00
|
|
|
const SALT_ROUNDS = 10;
|
|
|
|
|
|
2023-10-23 14:38:48 +02:00
|
|
|
let instance: UserCore | null;
|
|
|
|
|
|
2022-12-23 21:08:50 +01:00
|
|
|
export class UserCore {
|
2023-10-23 14:38:48 +02:00
|
|
|
private constructor(
|
2023-08-28 14:41:57 -05:00
|
|
|
private cryptoRepository: ICryptoRepository,
|
2023-10-23 14:38:48 +02:00
|
|
|
private libraryRepository: ILibraryRepository,
|
|
|
|
|
private userRepository: IUserRepository,
|
2023-08-28 14:41:57 -05:00
|
|
|
) {}
|
2022-12-23 21:08:50 +01:00
|
|
|
|
2023-10-23 14:38:48 +02:00
|
|
|
static create(
|
|
|
|
|
cryptoRepository: ICryptoRepository,
|
|
|
|
|
libraryRepository: ILibraryRepository,
|
|
|
|
|
userRepository: IUserRepository,
|
|
|
|
|
) {
|
|
|
|
|
if (!instance) {
|
|
|
|
|
instance = new UserCore(cryptoRepository, libraryRepository, userRepository);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static reset() {
|
|
|
|
|
instance = null;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-09 23:34:12 -05:00
|
|
|
// TODO: move auth related checks to the service layer
|
|
|
|
|
async updateUser(user: UserEntity | UserResponseDto, id: string, dto: Partial<UserEntity>): Promise<UserEntity> {
|
|
|
|
|
if (!user.isAdmin && user.id !== id) {
|
2022-12-23 21:08:50 +01:00
|
|
|
throw new ForbiddenException('You are not allowed to update this user');
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-09 23:34:12 -05:00
|
|
|
if (!user.isAdmin) {
|
2023-04-01 11:43:45 -05:00
|
|
|
// Users can never update the isAdmin property.
|
|
|
|
|
delete dto.isAdmin;
|
2023-05-21 23:18:10 -04:00
|
|
|
delete dto.storageLabel;
|
2023-12-09 23:34:12 -05:00
|
|
|
} else if (dto.isAdmin && user.id !== id) {
|
2023-04-01 11:43:45 -05:00
|
|
|
// Admin cannot create another admin.
|
|
|
|
|
throw new BadRequestException('The server already has an admin');
|
2022-12-23 21:08:50 +01:00
|
|
|
}
|
|
|
|
|
|
2022-12-27 11:36:31 -05:00
|
|
|
if (dto.email) {
|
|
|
|
|
const duplicate = await this.userRepository.getByEmail(dto.email);
|
|
|
|
|
if (duplicate && duplicate.id !== id) {
|
2023-05-21 23:18:10 -04:00
|
|
|
throw new BadRequestException('Email already in use by another account');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dto.storageLabel) {
|
|
|
|
|
const duplicate = await this.userRepository.getByStorageLabel(dto.storageLabel);
|
|
|
|
|
if (duplicate && duplicate.id !== id) {
|
|
|
|
|
throw new BadRequestException('Storage label already in use by another account');
|
2022-12-27 11:36:31 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-03 21:33:15 -04:00
|
|
|
if (dto.password) {
|
|
|
|
|
dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
|
|
|
|
|
}
|
2023-05-21 23:18:10 -04:00
|
|
|
|
2023-11-03 21:33:15 -04:00
|
|
|
if (dto.storageLabel === '') {
|
|
|
|
|
dto.storageLabel = null;
|
|
|
|
|
}
|
feat(server): support for read-only assets and importing existing items in the filesystem (#2715)
* Added read-only flag for assets, endpoint to trigger file import vs upload
* updated fixtures with new property
* if upload is 'read-only', ensure there is no existing asset at the designated originalPath
* added test for file import as well as detecting existing image at read-only destination location
* Added storage service test for a case where it should not move read-only assets
* upload doesn't need the read-only flag available, just importing
* default isReadOnly on import endpoint to true
* formatting fixes
* create-asset dto needs isReadOnly, so set it to false by default on create, updated api generation
* updated code to reflect changes in MR
* fixed read stream promise return type
* new index for originalPath, check for existing path on import, reglardless of user, to prevent duplicates
* refactor: import asset
* chore: open api
* chore: tests
* Added externalPath support for individual users, updated UI to allow this to be set by admin
* added missing var for externalPath in ui
* chore: open api
* fix: compilation issues
* fix: server test
* built api, fixed user-response dto to include externalPath
* reverted accidental commit
* bad commit of duplicate externalPath in user response dto
* fixed tests to include externalPath on expected result
* fix: unit tests
* centralized supported filetypes, perform file type checking of asset and sidecar during file import process
* centralized supported filetype check method to keep regex DRY
* fixed typo
* combined migrations into one
* update api
* Removed externalPath from shared-link code, added column to admin user page whether external paths / import is enabled or not
* update mimetype
* Fixed detect correct mimetype
* revert asset-upload config
* reverted domain.constant
* refactor
* fix mime-type issue
* fix format
---------
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-21 22:33:20 -04:00
|
|
|
|
2023-11-03 21:33:15 -04:00
|
|
|
return this.userRepository.update(id, dto);
|
2022-12-23 21:08:50 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-15 15:50:29 -04:00
|
|
|
async createUser(dto: Partial<UserEntity> & { email: string }): Promise<UserEntity> {
|
|
|
|
|
const user = await this.userRepository.getByEmail(dto.email);
|
2022-12-23 21:08:50 +01:00
|
|
|
if (user) {
|
|
|
|
|
throw new BadRequestException('User exists');
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-15 15:50:29 -04:00
|
|
|
if (!dto.isAdmin) {
|
2022-12-23 21:08:50 +01:00
|
|
|
const localAdmin = await this.userRepository.getAdmin();
|
|
|
|
|
if (!localAdmin) {
|
|
|
|
|
throw new BadRequestException('The first registered account must the administrator.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-03 21:33:15 -04:00
|
|
|
const payload: Partial<UserEntity> = { ...dto };
|
|
|
|
|
if (payload.password) {
|
|
|
|
|
payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
|
|
|
|
|
}
|
|
|
|
|
if (payload.storageLabel) {
|
2024-02-02 04:18:00 +01:00
|
|
|
payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
|
2022-12-23 21:08:50 +01:00
|
|
|
}
|
2023-11-03 21:33:15 -04:00
|
|
|
const userEntity = await this.userRepository.create(payload);
|
|
|
|
|
await this.libraryRepository.create({
|
|
|
|
|
owner: { id: userEntity.id } as UserEntity,
|
|
|
|
|
name: 'Default Library',
|
|
|
|
|
assets: [],
|
|
|
|
|
type: LibraryType.UPLOAD,
|
|
|
|
|
importPaths: [],
|
|
|
|
|
exclusionPatterns: [],
|
|
|
|
|
isVisible: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return userEntity;
|
2022-12-23 21:08:50 +01:00
|
|
|
}
|
|
|
|
|
}
|