mirror of
https://github.com/immich-app/immich.git
synced 2025-12-20 09:15:35 +03:00
feat(server,web): remove external path nonsense and make libraries admin-only (#7237)
* remove external path * open-api * make sql * move library settings to admin panel * Add documentation * show external libraries only * fix library list * make user library settings look good * fix test * fix tests * fix tests * can pick user for library * fix tests * fix e2e * chore: make sql * Use unauth exception * delete user library list * cleanup * fix e2e * fix await lint * chore: remove unused code * chore: cleanup * revert docs * fix: is admin stuff * table alignment --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
committed by
GitHub
parent
369acc7bea
commit
efa6efd200
@@ -1,59 +1,62 @@
|
||||
import { LibraryEntity, LibraryType } from '@app/infra/entities';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ArrayMaxSize, ArrayUnique, IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { ValidateUUID } from '../domain.util';
|
||||
import { ArrayMaxSize, ArrayUnique, IsBoolean, IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional, ValidateUUID } from '../domain.util';
|
||||
|
||||
export class CreateLibraryDto {
|
||||
@IsEnum(LibraryType)
|
||||
@ApiProperty({ enumName: 'LibraryType', enum: LibraryType })
|
||||
type!: LibraryType;
|
||||
|
||||
@ValidateUUID({ optional: true })
|
||||
ownerId?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsNotEmpty()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsBoolean()
|
||||
isVisible?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsString({ each: true })
|
||||
@IsNotEmpty({ each: true })
|
||||
@ArrayUnique()
|
||||
@ArrayMaxSize(128)
|
||||
importPaths?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsString({ each: true })
|
||||
@IsNotEmpty({ each: true })
|
||||
@ArrayUnique()
|
||||
@ArrayMaxSize(128)
|
||||
exclusionPatterns?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsBoolean()
|
||||
isWatched?: boolean;
|
||||
}
|
||||
|
||||
export class UpdateLibraryDto {
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsBoolean()
|
||||
isVisible?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsString({ each: true })
|
||||
@IsNotEmpty({ each: true })
|
||||
@ArrayUnique()
|
||||
@ArrayMaxSize(128)
|
||||
importPaths?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsNotEmpty({ each: true })
|
||||
@IsString({ each: true })
|
||||
@ArrayUnique()
|
||||
@@ -68,14 +71,14 @@ export class CrawlOptionsDto {
|
||||
}
|
||||
|
||||
export class ValidateLibraryDto {
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsString({ each: true })
|
||||
@IsNotEmpty({ each: true })
|
||||
@ArrayUnique()
|
||||
@ArrayMaxSize(128)
|
||||
importPaths?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsNotEmpty({ each: true })
|
||||
@IsString({ each: true })
|
||||
@ArrayUnique()
|
||||
@@ -100,14 +103,21 @@ export class LibrarySearchDto {
|
||||
|
||||
export class ScanLibraryDto {
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
refreshModifiedFiles?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
refreshAllFiles?: boolean = false;
|
||||
}
|
||||
|
||||
export class SearchLibraryDto {
|
||||
@IsEnum(LibraryType)
|
||||
@ApiProperty({ enumName: 'LibraryType', enum: LibraryType })
|
||||
@Optional()
|
||||
type?: LibraryType;
|
||||
}
|
||||
|
||||
export class LibraryResponseDto {
|
||||
id!: string;
|
||||
ownerId!: string;
|
||||
|
||||
@@ -140,24 +140,6 @@ describe(LibraryService.name, () => {
|
||||
});
|
||||
|
||||
describe('handleQueueAssetRefresh', () => {
|
||||
it("should not queue assets outside of user's external path", async () => {
|
||||
const mockLibraryJob: ILibraryRefreshJob = {
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
refreshModifiedFiles: false,
|
||||
refreshAllFiles: false,
|
||||
};
|
||||
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
storageMock.crawl.mockResolvedValue(['/data/user2/photo.jpg']);
|
||||
assetMock.getByLibraryId.mockResolvedValue([]);
|
||||
libraryMock.getOnlineAssetPaths.mockResolvedValue([]);
|
||||
userMock.get.mockResolvedValue(userStub.externalPath1);
|
||||
|
||||
await sut.handleQueueAssetRefresh(mockLibraryJob);
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([]);
|
||||
});
|
||||
|
||||
it('should queue new assets', async () => {
|
||||
const mockLibraryJob: ILibraryRefreshJob = {
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
@@ -168,8 +150,7 @@ describe(LibraryService.name, () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
storageMock.crawl.mockResolvedValue(['/data/user1/photo.jpg']);
|
||||
assetMock.getByLibraryId.mockResolvedValue([]);
|
||||
libraryMock.getOnlineAssetPaths.mockResolvedValue([]);
|
||||
userMock.get.mockResolvedValue(userStub.externalPath1);
|
||||
userMock.get.mockResolvedValue(userStub.admin);
|
||||
|
||||
await sut.handleQueueAssetRefresh(mockLibraryJob);
|
||||
|
||||
@@ -196,8 +177,7 @@ describe(LibraryService.name, () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
storageMock.crawl.mockResolvedValue(['/data/user1/photo.jpg']);
|
||||
assetMock.getByLibraryId.mockResolvedValue([]);
|
||||
libraryMock.getOnlineAssetPaths.mockResolvedValue([]);
|
||||
userMock.get.mockResolvedValue(userStub.externalPath1);
|
||||
userMock.get.mockResolvedValue(userStub.admin);
|
||||
|
||||
await sut.handleQueueAssetRefresh(mockLibraryJob);
|
||||
|
||||
@@ -214,45 +194,6 @@ describe(LibraryService.name, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("should mark assets outside of the user's external path as offline", async () => {
|
||||
const mockLibraryJob: ILibraryRefreshJob = {
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
refreshModifiedFiles: false,
|
||||
refreshAllFiles: false,
|
||||
};
|
||||
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
storageMock.crawl.mockResolvedValue(['/data/user1/photo.jpg']);
|
||||
assetMock.getByLibraryId.mockResolvedValue([assetStub.external]);
|
||||
libraryMock.getOnlineAssetPaths.mockResolvedValue([]);
|
||||
userMock.get.mockResolvedValue(userStub.externalPath2);
|
||||
|
||||
await sut.handleQueueAssetRefresh(mockLibraryJob);
|
||||
|
||||
expect(assetMock.updateAll.mock.calls).toEqual([
|
||||
[
|
||||
[assetStub.external.id],
|
||||
{
|
||||
isOffline: true,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not scan libraries owned by user without external path', async () => {
|
||||
const mockLibraryJob: ILibraryRefreshJob = {
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
refreshModifiedFiles: false,
|
||||
refreshAllFiles: false,
|
||||
};
|
||||
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
|
||||
await expect(sut.handleQueueAssetRefresh(mockLibraryJob)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('should not scan upload libraries', async () => {
|
||||
const mockLibraryJob: ILibraryRefreshJob = {
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
@@ -287,7 +228,6 @@ describe(LibraryService.name, () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
|
||||
storageMock.crawl.mockResolvedValue([]);
|
||||
assetMock.getByLibraryId.mockResolvedValue([]);
|
||||
libraryMock.getOnlineAssetPaths.mockResolvedValue([]);
|
||||
userMock.get.mockResolvedValue(userStub.externalPathRoot);
|
||||
|
||||
await sut.handleQueueAssetRefresh(mockLibraryJob);
|
||||
@@ -303,7 +243,7 @@ describe(LibraryService.name, () => {
|
||||
let mockUser: UserEntity;
|
||||
|
||||
beforeEach(() => {
|
||||
mockUser = userStub.externalPath1;
|
||||
mockUser = userStub.admin;
|
||||
userMock.get.mockResolvedValue(mockUser);
|
||||
|
||||
storageMock.stat.mockResolvedValue({
|
||||
@@ -780,26 +720,6 @@ describe(LibraryService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllForUser', () => {
|
||||
it('should return all libraries for user', async () => {
|
||||
libraryMock.getAllByUserId.mockResolvedValue([libraryStub.uploadLibrary1, libraryStub.externalLibrary1]);
|
||||
await expect(sut.getAllForUser(authStub.admin)).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
id: libraryStub.uploadLibrary1.id,
|
||||
name: libraryStub.uploadLibrary1.name,
|
||||
ownerId: libraryStub.uploadLibrary1.ownerId,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
name: libraryStub.externalLibrary1.name,
|
||||
ownerId: libraryStub.externalLibrary1.ownerId,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(libraryMock.getAllByUserId).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStatistics', () => {
|
||||
it('should return library statistics', async () => {
|
||||
libraryMock.getStatistics.mockResolvedValue({ photos: 10, videos: 0, total: 10, usage: 1337 });
|
||||
@@ -1144,12 +1064,12 @@ describe(LibraryService.name, () => {
|
||||
storageMock.checkFileExists.mockResolvedValue(true);
|
||||
|
||||
await expect(
|
||||
sut.update(authStub.external1, authStub.external1.user.id, { importPaths: ['/data/user1/foo'] }),
|
||||
sut.update(authStub.admin, authStub.admin.user.id, { importPaths: ['/data/user1/foo'] }),
|
||||
).resolves.toEqual(mapLibrary(libraryStub.externalLibraryWithImportPaths1));
|
||||
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: authStub.external1.user.id,
|
||||
id: authStub.admin.user.id,
|
||||
}),
|
||||
);
|
||||
expect(storageMock.watch).toHaveBeenCalledWith(
|
||||
@@ -1584,26 +1504,6 @@ describe(LibraryService.name, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should error when no external path is set', async () => {
|
||||
await expect(
|
||||
sut.validate(authStub.admin, libraryStub.externalLibrary1.id, { importPaths: ['/photos'] }),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('should detect when path is outside external path', async () => {
|
||||
const result = await sut.validate(authStub.external1, libraryStub.externalLibraryWithImportPaths1.id, {
|
||||
importPaths: ['/data/user2'],
|
||||
});
|
||||
|
||||
expect(result.importPaths).toEqual([
|
||||
{
|
||||
importPath: '/data/user2',
|
||||
isValid: false,
|
||||
message: "Not contained in user's external path",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect when path does not exist', async () => {
|
||||
storageMock.stat.mockImplementation(() => {
|
||||
const error = { code: 'ENOENT' } as any;
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
LibraryResponseDto,
|
||||
LibraryStatsResponseDto,
|
||||
ScanLibraryDto,
|
||||
SearchLibraryDto,
|
||||
UpdateLibraryDto,
|
||||
ValidateLibraryDto,
|
||||
ValidateLibraryImportPathResponseDto,
|
||||
@@ -182,6 +183,7 @@ export class LibraryService extends EventEmitter {
|
||||
|
||||
async getStatistics(auth: AuthDto, id: string): Promise<LibraryStatsResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.LIBRARY_READ, id);
|
||||
|
||||
return this.repository.getStatistics(id);
|
||||
}
|
||||
|
||||
@@ -189,17 +191,18 @@ export class LibraryService extends EventEmitter {
|
||||
return this.repository.getCountForUser(auth.user.id);
|
||||
}
|
||||
|
||||
async getAllForUser(auth: AuthDto): Promise<LibraryResponseDto[]> {
|
||||
const libraries = await this.repository.getAllByUserId(auth.user.id);
|
||||
return libraries.map((library) => mapLibrary(library));
|
||||
}
|
||||
|
||||
async get(auth: AuthDto, id: string): Promise<LibraryResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.LIBRARY_READ, id);
|
||||
|
||||
const library = await this.findOrFail(id);
|
||||
return mapLibrary(library);
|
||||
}
|
||||
|
||||
async getAll(auth: AuthDto, dto: SearchLibraryDto): Promise<LibraryResponseDto[]> {
|
||||
const libraries = await this.repository.getAll(false, dto.type);
|
||||
return libraries.map((library) => mapLibrary(library));
|
||||
}
|
||||
|
||||
async handleQueueCleanup(): Promise<boolean> {
|
||||
this.logger.debug('Cleaning up any pending library deletions');
|
||||
const pendingDeletion = await this.repository.getAllDeleted();
|
||||
@@ -234,8 +237,14 @@ export class LibraryService extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
let ownerId = auth.user.id;
|
||||
|
||||
if (dto.ownerId) {
|
||||
ownerId = dto.ownerId;
|
||||
}
|
||||
|
||||
const library = await this.repository.create({
|
||||
ownerId: auth.user.id,
|
||||
ownerId,
|
||||
name: dto.name,
|
||||
type: dto.type,
|
||||
importPaths: dto.importPaths ?? [],
|
||||
@@ -300,24 +309,11 @@ export class LibraryService extends EventEmitter {
|
||||
public async validate(auth: AuthDto, id: string, dto: ValidateLibraryDto): Promise<ValidateLibraryResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.LIBRARY_UPDATE, id);
|
||||
|
||||
if (!auth.user.externalPath) {
|
||||
throw new BadRequestException('User has no external path set');
|
||||
}
|
||||
|
||||
const response = new ValidateLibraryResponseDto();
|
||||
|
||||
if (dto.importPaths) {
|
||||
response.importPaths = await Promise.all(
|
||||
dto.importPaths.map(async (importPath) => {
|
||||
const normalizedPath = path.normalize(importPath);
|
||||
|
||||
if (!this.isInExternalPath(normalizedPath, auth.user.externalPath)) {
|
||||
const validation = new ValidateLibraryImportPathResponseDto();
|
||||
validation.importPath = importPath;
|
||||
validation.message = `Not contained in user's external path`;
|
||||
return validation;
|
||||
}
|
||||
|
||||
return await this.validateImportPath(importPath);
|
||||
}),
|
||||
);
|
||||
@@ -328,6 +324,7 @@ export class LibraryService extends EventEmitter {
|
||||
|
||||
async update(auth: AuthDto, id: string, dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.LIBRARY_UPDATE, id);
|
||||
|
||||
const library = await this.repository.update({ id, ...dto });
|
||||
|
||||
if (dto.importPaths) {
|
||||
@@ -404,7 +401,7 @@ export class LibraryService extends EventEmitter {
|
||||
return true;
|
||||
} else {
|
||||
// File can't be accessed and does not already exist in db
|
||||
throw new BadRequestException("Can't access file", { cause: error });
|
||||
throw new BadRequestException('Cannot access file', { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,12 +588,6 @@ export class LibraryService extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
|
||||
const user = await this.userRepository.get(library.ownerId, {});
|
||||
if (!user?.externalPath) {
|
||||
this.logger.warn('User has no external path set, cannot refresh library');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.verbose(`Refreshing library: ${job.id}`);
|
||||
|
||||
const pathValidation = await Promise.all(
|
||||
@@ -618,11 +609,7 @@ export class LibraryService extends EventEmitter {
|
||||
exclusionPatterns: library.exclusionPatterns,
|
||||
});
|
||||
|
||||
const crawledAssetPaths = rawPaths
|
||||
// Normalize file paths. This is important to prevent security issues like path traversal
|
||||
.map((filePath) => path.normalize(filePath))
|
||||
// Filter out paths that are not within the user's external path
|
||||
.filter((assetPath) => this.isInExternalPath(assetPath, user.externalPath)) as string[];
|
||||
const crawledAssetPaths = rawPaths.map((filePath) => path.normalize(filePath));
|
||||
|
||||
this.logger.debug(`Found ${crawledAssetPaths.length} asset(s) when crawling import paths ${library.importPaths}`);
|
||||
const assetsInLibrary = await this.assetRepository.getByLibraryId([job.id]);
|
||||
|
||||
@@ -18,7 +18,6 @@ const responseDto = {
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
externalPath: null,
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
quotaSizeInBytes: null,
|
||||
@@ -37,7 +36,6 @@ const responseDto = {
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
externalPath: null,
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
inTimeline: true,
|
||||
|
||||
@@ -5,7 +5,6 @@ export const ILibraryRepository = 'ILibraryRepository';
|
||||
|
||||
export interface ILibraryRepository {
|
||||
getCountForUser(ownerId: string): Promise<number>;
|
||||
getAllByUserId(userId: string, type?: LibraryType): Promise<LibraryEntity[]>;
|
||||
getAll(withDeleted?: boolean, type?: LibraryType): Promise<LibraryEntity[]>;
|
||||
getAllDeleted(): Promise<LibraryEntity[]>;
|
||||
get(id: string, withDeleted?: boolean): Promise<LibraryEntity | null>;
|
||||
@@ -16,7 +15,5 @@ export interface ILibraryRepository {
|
||||
getUploadLibraryCount(ownerId: string): Promise<number>;
|
||||
update(library: Partial<LibraryEntity>): Promise<LibraryEntity>;
|
||||
getStatistics(id: string): Promise<LibraryStatsResponseDto>;
|
||||
getOnlineAssetPaths(id: string): Promise<string[]>;
|
||||
getAssetIds(id: string, withDeleted?: boolean): Promise<string[]>;
|
||||
existsByName(name: string, withDeleted?: boolean): Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional } from '../../domain.util';
|
||||
|
||||
export enum SearchSuggestionType {
|
||||
COUNTRY = 'country',
|
||||
@@ -16,18 +17,18 @@ export class SearchSuggestionRequestDto {
|
||||
type!: SearchSuggestionType;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
country?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
state?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
make?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
model?: string;
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ export class CreateUserDto {
|
||||
@Transform(toSanitized)
|
||||
storageLabel?: string | null;
|
||||
|
||||
@Optional({ nullable: true })
|
||||
@IsString()
|
||||
externalPath?: string | null;
|
||||
|
||||
@Optional()
|
||||
@IsBoolean()
|
||||
memoriesEnabled?: boolean;
|
||||
|
||||
@@ -25,10 +25,6 @@ export class UpdateUserDto {
|
||||
@Transform(toSanitized)
|
||||
storageLabel?: string;
|
||||
|
||||
@Optional()
|
||||
@IsString()
|
||||
externalPath?: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsUUID('4')
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
|
||||
@@ -22,7 +22,6 @@ export class UserDto {
|
||||
|
||||
export class UserResponseDto extends UserDto {
|
||||
storageLabel!: string | null;
|
||||
externalPath!: string | null;
|
||||
shouldChangePassword!: boolean;
|
||||
isAdmin!: boolean;
|
||||
createdAt!: Date;
|
||||
@@ -50,7 +49,6 @@ export function mapUser(entity: UserEntity): UserResponseDto {
|
||||
return {
|
||||
...mapSimpleUser(entity),
|
||||
storageLabel: entity.storageLabel,
|
||||
externalPath: entity.externalPath,
|
||||
shouldChangePassword: entity.shouldChangePassword,
|
||||
isAdmin: entity.isAdmin,
|
||||
createdAt: entity.createdAt,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { LibraryType, UserEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||
import path from 'node:path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { ICryptoRepository, ILibraryRepository, IUserRepository } from '../repositories';
|
||||
import { UserResponseDto } from './response-dto';
|
||||
@@ -42,7 +41,6 @@ export class UserCore {
|
||||
// Users can never update the isAdmin property.
|
||||
delete dto.isAdmin;
|
||||
delete dto.storageLabel;
|
||||
delete dto.externalPath;
|
||||
} else if (dto.isAdmin && user.id !== id) {
|
||||
// Admin cannot create another admin.
|
||||
throw new BadRequestException('The server already has an admin');
|
||||
@@ -70,12 +68,6 @@ export class UserCore {
|
||||
dto.storageLabel = null;
|
||||
}
|
||||
|
||||
if (dto.externalPath === '') {
|
||||
dto.externalPath = null;
|
||||
} else if (dto.externalPath) {
|
||||
dto.externalPath = path.normalize(dto.externalPath);
|
||||
}
|
||||
|
||||
return this.userRepository.update(id, dto);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@ import {
|
||||
LibraryStatsResponseDto,
|
||||
LibraryResponseDto as ResponseDto,
|
||||
ScanLibraryDto,
|
||||
SearchLibraryDto,
|
||||
UpdateLibraryDto as UpdateDto,
|
||||
ValidateLibraryDto,
|
||||
ValidateLibraryResponseDto,
|
||||
} from '@app/domain';
|
||||
import { Body, Controller, Delete, Get, HttpCode, Param, Post, Put } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, HttpCode, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { AdminRoute, Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
@@ -19,12 +20,13 @@ import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
@Controller('library')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
@AdminRoute()
|
||||
export class LibraryController {
|
||||
constructor(private service: LibraryService) {}
|
||||
|
||||
@Get()
|
||||
getLibraries(@Auth() auth: AuthDto): Promise<ResponseDto[]> {
|
||||
return this.service.getAllForUser(auth);
|
||||
getAllLibraries(@Auth() auth: AuthDto, @Query() dto: SearchLibraryDto): Promise<ResponseDto[]> {
|
||||
return this.service.getAll(auth, dto);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@@ -38,7 +40,7 @@ export class LibraryController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getLibraryInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<ResponseDto> {
|
||||
getLibrary(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<ResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,9 +43,6 @@ export class UserEntity {
|
||||
@Column({ type: 'varchar', unique: true, default: null })
|
||||
storageLabel!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', default: null })
|
||||
externalPath!: string | null;
|
||||
|
||||
@Column({ default: '', select: false })
|
||||
password?: string;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveExternalPath1708425975121 implements MigrationInterface {
|
||||
name = 'RemoveExternalPath1708425975121';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "externalPath"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "externalPath" character varying`);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ FROM
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||
@@ -37,7 +36,6 @@ FROM
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||
@@ -97,7 +95,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||
@@ -113,7 +110,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||
@@ -155,7 +151,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||
@@ -171,7 +166,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||
@@ -285,7 +279,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||
@@ -313,7 +306,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||
@@ -358,7 +350,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||
@@ -386,7 +377,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||
@@ -468,7 +458,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||
@@ -496,7 +485,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||
@@ -559,7 +547,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||
|
||||
@@ -15,7 +15,6 @@ FROM
|
||||
"APIKeyEntity__APIKeyEntity_user"."isAdmin" AS "APIKeyEntity__APIKeyEntity_user_isAdmin",
|
||||
"APIKeyEntity__APIKeyEntity_user"."email" AS "APIKeyEntity__APIKeyEntity_user_email",
|
||||
"APIKeyEntity__APIKeyEntity_user"."storageLabel" AS "APIKeyEntity__APIKeyEntity_user_storageLabel",
|
||||
"APIKeyEntity__APIKeyEntity_user"."externalPath" AS "APIKeyEntity__APIKeyEntity_user_externalPath",
|
||||
"APIKeyEntity__APIKeyEntity_user"."oauthId" AS "APIKeyEntity__APIKeyEntity_user_oauthId",
|
||||
"APIKeyEntity__APIKeyEntity_user"."profileImagePath" AS "APIKeyEntity__APIKeyEntity_user_profileImagePath",
|
||||
"APIKeyEntity__APIKeyEntity_user"."shouldChangePassword" AS "APIKeyEntity__APIKeyEntity_user_shouldChangePassword",
|
||||
|
||||
@@ -23,7 +23,6 @@ FROM
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
"LibraryEntity__LibraryEntity_owner"."externalPath" AS "LibraryEntity__LibraryEntity_owner_externalPath",
|
||||
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||
@@ -139,7 +138,6 @@ SELECT
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
"LibraryEntity__LibraryEntity_owner"."externalPath" AS "LibraryEntity__LibraryEntity_owner_externalPath",
|
||||
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||
@@ -185,7 +183,6 @@ SELECT
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
"LibraryEntity__LibraryEntity_owner"."externalPath" AS "LibraryEntity__LibraryEntity_owner_externalPath",
|
||||
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||
@@ -225,7 +222,6 @@ SELECT
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
"LibraryEntity__LibraryEntity_owner"."externalPath" AS "LibraryEntity__LibraryEntity_owner_externalPath",
|
||||
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||
|
||||
@@ -150,7 +150,6 @@ FROM
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."isAdmin" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_isAdmin",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."email" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_email",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."storageLabel" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_storageLabel",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."externalPath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_externalPath",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."oauthId" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_oauthId",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."profileImagePath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_profileImagePath",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."shouldChangePassword" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_shouldChangePassword",
|
||||
@@ -254,7 +253,6 @@ SELECT
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."isAdmin" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_isAdmin",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."email" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_email",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."storageLabel" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_storageLabel",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."externalPath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_externalPath",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."oauthId" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_oauthId",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."profileImagePath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_profileImagePath",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."shouldChangePassword" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_shouldChangePassword",
|
||||
@@ -308,7 +306,6 @@ FROM
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."isAdmin" AS "SharedLinkEntity__SharedLinkEntity_user_isAdmin",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."email" AS "SharedLinkEntity__SharedLinkEntity_user_email",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."storageLabel" AS "SharedLinkEntity__SharedLinkEntity_user_storageLabel",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."externalPath" AS "SharedLinkEntity__SharedLinkEntity_user_externalPath",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."oauthId" AS "SharedLinkEntity__SharedLinkEntity_user_oauthId",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."profileImagePath" AS "SharedLinkEntity__SharedLinkEntity_user_profileImagePath",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."shouldChangePassword" AS "SharedLinkEntity__SharedLinkEntity_user_shouldChangePassword",
|
||||
|
||||
@@ -8,7 +8,6 @@ SELECT
|
||||
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||
"UserEntity"."email" AS "UserEntity_email",
|
||||
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||
"UserEntity"."externalPath" AS "UserEntity_externalPath",
|
||||
"UserEntity"."oauthId" AS "UserEntity_oauthId",
|
||||
"UserEntity"."profileImagePath" AS "UserEntity_profileImagePath",
|
||||
"UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword",
|
||||
@@ -55,7 +54,6 @@ SELECT
|
||||
"user"."isAdmin" AS "user_isAdmin",
|
||||
"user"."email" AS "user_email",
|
||||
"user"."storageLabel" AS "user_storageLabel",
|
||||
"user"."externalPath" AS "user_externalPath",
|
||||
"user"."oauthId" AS "user_oauthId",
|
||||
"user"."profileImagePath" AS "user_profileImagePath",
|
||||
"user"."shouldChangePassword" AS "user_shouldChangePassword",
|
||||
@@ -79,7 +77,6 @@ SELECT
|
||||
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||
"UserEntity"."email" AS "UserEntity_email",
|
||||
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||
"UserEntity"."externalPath" AS "UserEntity_externalPath",
|
||||
"UserEntity"."oauthId" AS "UserEntity_oauthId",
|
||||
"UserEntity"."profileImagePath" AS "UserEntity_profileImagePath",
|
||||
"UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword",
|
||||
@@ -105,7 +102,6 @@ SELECT
|
||||
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||
"UserEntity"."email" AS "UserEntity_email",
|
||||
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||
"UserEntity"."externalPath" AS "UserEntity_externalPath",
|
||||
"UserEntity"."oauthId" AS "UserEntity_oauthId",
|
||||
"UserEntity"."profileImagePath" AS "UserEntity_profileImagePath",
|
||||
"UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword",
|
||||
|
||||
@@ -18,7 +18,6 @@ FROM
|
||||
"UserTokenEntity__UserTokenEntity_user"."isAdmin" AS "UserTokenEntity__UserTokenEntity_user_isAdmin",
|
||||
"UserTokenEntity__UserTokenEntity_user"."email" AS "UserTokenEntity__UserTokenEntity_user_email",
|
||||
"UserTokenEntity__UserTokenEntity_user"."storageLabel" AS "UserTokenEntity__UserTokenEntity_user_storageLabel",
|
||||
"UserTokenEntity__UserTokenEntity_user"."externalPath" AS "UserTokenEntity__UserTokenEntity_user_externalPath",
|
||||
"UserTokenEntity__UserTokenEntity_user"."oauthId" AS "UserTokenEntity__UserTokenEntity_user_oauthId",
|
||||
"UserTokenEntity__UserTokenEntity_user"."profileImagePath" AS "UserTokenEntity__UserTokenEntity_user_profileImagePath",
|
||||
"UserTokenEntity__UserTokenEntity_user"."shouldChangePassword" AS "UserTokenEntity__UserTokenEntity_user_shouldChangePassword",
|
||||
|
||||
Reference in New Issue
Block a user