mirror of
https://github.com/immich-app/immich.git
synced 2025-04-21 15:36:26 +02:00
refactor(server): auth dto (#5593)
* refactor: AuthUserDto => AuthDto * refactor: reorganize auth-dto * refactor: AuthUser() => Auth()
This commit is contained in:
parent
8057c375ba
commit
33529d1d9b
60 changed files with 1033 additions and 1065 deletions
server
src
domain
access
activity
album
api-key
asset
audit
auth
library
partner
person
search
shared-link
tag
user
immich-admin
immich
api-v1/asset
app.guard.tscontrollers
activity.controller.tsalbum.controller.tsapi-key.controller.tsasset.controller.tsaudit.controller.tsauth.controller.tsface.controller.tslibrary.controller.tsoauth.controller.tspartner.controller.tsperson.controller.tssearch.controller.tsshared-link.controller.tstag.controller.tsuser.controller.ts
interceptors
infra/repositories
test/fixtures
|
@ -1,5 +1,6 @@
|
|||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { SharedLinkEntity } from '../../infra/entities';
|
||||
import { AuthDto } from '../auth';
|
||||
import { setDifference, setIsEqual, setUnion } from '../domain.util';
|
||||
import { IAccessRepository } from '../repositories';
|
||||
|
||||
|
@ -64,20 +65,20 @@ export class AccessCore {
|
|||
instance = null;
|
||||
}
|
||||
|
||||
requireUploadAccess(authUser: AuthUserDto | null): AuthUserDto {
|
||||
if (!authUser || (authUser.isPublicUser && !authUser.isAllowUpload)) {
|
||||
requireUploadAccess(auth: AuthDto | null): AuthDto {
|
||||
if (!auth || (auth.sharedLink && !auth.sharedLink.allowUpload)) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return authUser;
|
||||
return auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has access to all ids, for the given permission.
|
||||
* Throws error if user does not have access to any of the ids.
|
||||
*/
|
||||
async requirePermission(authUser: AuthUserDto, permission: Permission, ids: string[] | string) {
|
||||
async requirePermission(auth: AuthDto, permission: Permission, ids: string[] | string) {
|
||||
ids = Array.isArray(ids) ? ids : [ids];
|
||||
const allowedIds = await this.checkAccess(authUser, permission, ids);
|
||||
const allowedIds = await this.checkAccess(auth, permission, ids);
|
||||
if (!setIsEqual(new Set(ids), allowedIds)) {
|
||||
throw new BadRequestException(`Not found or no ${permission} access`);
|
||||
}
|
||||
|
@ -89,23 +90,21 @@ export class AccessCore {
|
|||
*
|
||||
* @returns Set<string>
|
||||
*/
|
||||
async checkAccess(authUser: AuthUserDto, permission: Permission, ids: Set<string> | string[]) {
|
||||
async checkAccess(auth: AuthDto, permission: Permission, ids: Set<string> | string[]) {
|
||||
const idSet = Array.isArray(ids) ? new Set(ids) : ids;
|
||||
if (idSet.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const isSharedLink = authUser.isPublicUser ?? false;
|
||||
return isSharedLink
|
||||
? await this.checkAccessSharedLink(authUser, permission, idSet)
|
||||
: await this.checkAccessOther(authUser, permission, idSet);
|
||||
if (auth.sharedLink) {
|
||||
return this.checkAccessSharedLink(auth.sharedLink, permission, idSet);
|
||||
}
|
||||
|
||||
return this.checkAccessOther(auth, permission, idSet);
|
||||
}
|
||||
|
||||
private async checkAccessSharedLink(authUser: AuthUserDto, permission: Permission, ids: Set<string>) {
|
||||
const sharedLinkId = authUser.sharedLinkId;
|
||||
if (!sharedLinkId) {
|
||||
return new Set();
|
||||
}
|
||||
private async checkAccessSharedLink(sharedLink: SharedLinkEntity, permission: Permission, ids: Set<string>) {
|
||||
const sharedLinkId = sharedLink.id;
|
||||
|
||||
switch (permission) {
|
||||
case Permission.ASSET_READ:
|
||||
|
@ -115,22 +114,22 @@ export class AccessCore {
|
|||
return await this.repository.asset.checkSharedLinkAccess(sharedLinkId, ids);
|
||||
|
||||
case Permission.ASSET_DOWNLOAD:
|
||||
return !!authUser.isAllowDownload
|
||||
return !!sharedLink.allowDownload
|
||||
? await this.repository.asset.checkSharedLinkAccess(sharedLinkId, ids)
|
||||
: new Set();
|
||||
|
||||
case Permission.ASSET_UPLOAD:
|
||||
return authUser.isAllowUpload ? ids : new Set();
|
||||
return sharedLink.allowUpload ? ids : new Set();
|
||||
|
||||
case Permission.ASSET_SHARE:
|
||||
// TODO: fix this to not use authUser.id for shared link access control
|
||||
return await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||
// TODO: fix this to not use sharedLink.userId for access control
|
||||
return await this.repository.asset.checkOwnerAccess(sharedLink.userId, ids);
|
||||
|
||||
case Permission.ALBUM_READ:
|
||||
return await this.repository.album.checkSharedLinkAccess(sharedLinkId, ids);
|
||||
|
||||
case Permission.ALBUM_DOWNLOAD:
|
||||
return !!authUser.isAllowDownload
|
||||
return !!sharedLink.allowDownload
|
||||
? await this.repository.album.checkSharedLinkAccess(sharedLinkId, ids)
|
||||
: new Set();
|
||||
|
||||
|
@ -139,129 +138,129 @@ export class AccessCore {
|
|||
}
|
||||
}
|
||||
|
||||
private async checkAccessOther(authUser: AuthUserDto, permission: Permission, ids: Set<string>) {
|
||||
private async checkAccessOther(auth: AuthDto, permission: Permission, ids: Set<string>) {
|
||||
switch (permission) {
|
||||
case Permission.ASSET_READ: {
|
||||
const isOwner = await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||
const isAlbum = await this.repository.asset.checkAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||
const isOwner = await this.repository.asset.checkOwnerAccess(auth.user.id, ids);
|
||||
const isAlbum = await this.repository.asset.checkAlbumAccess(auth.user.id, setDifference(ids, isOwner));
|
||||
const isPartner = await this.repository.asset.checkPartnerAccess(
|
||||
authUser.id,
|
||||
auth.user.id,
|
||||
setDifference(ids, isOwner, isAlbum),
|
||||
);
|
||||
return setUnion(isOwner, isAlbum, isPartner);
|
||||
}
|
||||
|
||||
case Permission.ASSET_SHARE: {
|
||||
const isOwner = await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||
const isPartner = await this.repository.asset.checkPartnerAccess(authUser.id, setDifference(ids, isOwner));
|
||||
const isOwner = await this.repository.asset.checkOwnerAccess(auth.user.id, ids);
|
||||
const isPartner = await this.repository.asset.checkPartnerAccess(auth.user.id, setDifference(ids, isOwner));
|
||||
return setUnion(isOwner, isPartner);
|
||||
}
|
||||
|
||||
case Permission.ASSET_VIEW: {
|
||||
const isOwner = await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||
const isAlbum = await this.repository.asset.checkAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||
const isOwner = await this.repository.asset.checkOwnerAccess(auth.user.id, ids);
|
||||
const isAlbum = await this.repository.asset.checkAlbumAccess(auth.user.id, setDifference(ids, isOwner));
|
||||
const isPartner = await this.repository.asset.checkPartnerAccess(
|
||||
authUser.id,
|
||||
auth.user.id,
|
||||
setDifference(ids, isOwner, isAlbum),
|
||||
);
|
||||
return setUnion(isOwner, isAlbum, isPartner);
|
||||
}
|
||||
|
||||
case Permission.ASSET_DOWNLOAD: {
|
||||
const isOwner = await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||
const isAlbum = await this.repository.asset.checkAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||
const isOwner = await this.repository.asset.checkOwnerAccess(auth.user.id, ids);
|
||||
const isAlbum = await this.repository.asset.checkAlbumAccess(auth.user.id, setDifference(ids, isOwner));
|
||||
const isPartner = await this.repository.asset.checkPartnerAccess(
|
||||
authUser.id,
|
||||
auth.user.id,
|
||||
setDifference(ids, isOwner, isAlbum),
|
||||
);
|
||||
return setUnion(isOwner, isAlbum, isPartner);
|
||||
}
|
||||
|
||||
case Permission.ASSET_UPDATE:
|
||||
return await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.asset.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.ASSET_DELETE:
|
||||
return await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.asset.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.ASSET_RESTORE:
|
||||
return await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.asset.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.ALBUM_READ: {
|
||||
const isOwner = await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
const isShared = await this.repository.album.checkSharedAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||
const isOwner = await this.repository.album.checkOwnerAccess(auth.user.id, ids);
|
||||
const isShared = await this.repository.album.checkSharedAlbumAccess(auth.user.id, setDifference(ids, isOwner));
|
||||
return setUnion(isOwner, isShared);
|
||||
}
|
||||
|
||||
case Permission.ALBUM_UPDATE:
|
||||
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.album.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.ALBUM_DELETE:
|
||||
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.album.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.ALBUM_SHARE:
|
||||
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.album.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.ALBUM_DOWNLOAD: {
|
||||
const isOwner = await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
const isShared = await this.repository.album.checkSharedAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||
const isOwner = await this.repository.album.checkOwnerAccess(auth.user.id, ids);
|
||||
const isShared = await this.repository.album.checkSharedAlbumAccess(auth.user.id, setDifference(ids, isOwner));
|
||||
return setUnion(isOwner, isShared);
|
||||
}
|
||||
|
||||
case Permission.ALBUM_REMOVE_ASSET:
|
||||
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.album.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.ASSET_UPLOAD:
|
||||
return await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.library.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.ARCHIVE_READ:
|
||||
return ids.has(authUser.id) ? new Set([authUser.id]) : new Set();
|
||||
return ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set();
|
||||
|
||||
case Permission.AUTH_DEVICE_DELETE:
|
||||
return await this.repository.authDevice.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.authDevice.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.TIMELINE_READ: {
|
||||
const isOwner = ids.has(authUser.id) ? new Set([authUser.id]) : new Set<string>();
|
||||
const isPartner = await this.repository.timeline.checkPartnerAccess(authUser.id, setDifference(ids, isOwner));
|
||||
const isOwner = ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set<string>();
|
||||
const isPartner = await this.repository.timeline.checkPartnerAccess(auth.user.id, setDifference(ids, isOwner));
|
||||
return setUnion(isOwner, isPartner);
|
||||
}
|
||||
|
||||
case Permission.TIMELINE_DOWNLOAD:
|
||||
return ids.has(authUser.id) ? new Set([authUser.id]) : new Set();
|
||||
return ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set();
|
||||
|
||||
case Permission.LIBRARY_READ: {
|
||||
const isOwner = await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||
const isPartner = await this.repository.library.checkPartnerAccess(authUser.id, setDifference(ids, isOwner));
|
||||
const isOwner = await this.repository.library.checkOwnerAccess(auth.user.id, ids);
|
||||
const isPartner = await this.repository.library.checkPartnerAccess(auth.user.id, setDifference(ids, isOwner));
|
||||
return setUnion(isOwner, isPartner);
|
||||
}
|
||||
|
||||
case Permission.LIBRARY_UPDATE:
|
||||
return await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.library.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.LIBRARY_DELETE:
|
||||
return await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.library.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.PERSON_READ:
|
||||
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.person.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.PERSON_WRITE:
|
||||
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.person.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.PERSON_MERGE:
|
||||
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.person.checkOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.PERSON_CREATE:
|
||||
return this.repository.person.hasFaceOwnerAccess(authUser.id, ids);
|
||||
return this.repository.person.hasFaceOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.PERSON_REASSIGN:
|
||||
return this.repository.person.hasFaceOwnerAccess(authUser.id, ids);
|
||||
return this.repository.person.hasFaceOwnerAccess(auth.user.id, ids);
|
||||
|
||||
case Permission.PARTNER_UPDATE:
|
||||
return await this.repository.partner.checkUpdateAccess(authUser.id, ids);
|
||||
return await this.repository.partner.checkUpdateAccess(auth.user.id, ids);
|
||||
}
|
||||
|
||||
const allowedIds = new Set();
|
||||
for (const id of ids) {
|
||||
const hasAccess = await this.hasOtherAccess(authUser, permission, id);
|
||||
const hasAccess = await this.hasOtherAccess(auth, permission, id);
|
||||
if (hasAccess) {
|
||||
allowedIds.add(id);
|
||||
}
|
||||
|
@ -270,17 +269,17 @@ export class AccessCore {
|
|||
}
|
||||
|
||||
// TODO: Migrate logic to checkAccessOther to evaluate permissions in bulk.
|
||||
private async hasOtherAccess(authUser: AuthUserDto, permission: Permission, id: string) {
|
||||
private async hasOtherAccess(auth: AuthDto, permission: Permission, id: string) {
|
||||
switch (permission) {
|
||||
// uses album id
|
||||
case Permission.ACTIVITY_CREATE:
|
||||
return await this.repository.activity.hasCreateAccess(authUser.id, id);
|
||||
return await this.repository.activity.hasCreateAccess(auth.user.id, id);
|
||||
|
||||
// uses activity id
|
||||
case Permission.ACTIVITY_DELETE:
|
||||
return (
|
||||
(await this.repository.activity.hasOwnerAccess(authUser.id, id)) ||
|
||||
(await this.repository.activity.hasAlbumOwnerAccess(authUser.id, id))
|
||||
(await this.repository.activity.hasOwnerAccess(auth.user.id, id)) ||
|
||||
(await this.repository.activity.hasAlbumOwnerAccess(auth.user.id, id))
|
||||
);
|
||||
|
||||
default:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ActivityEntity } from '@app/infra/entities';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { IAccessRepository, IActivityRepository } from '../repositories';
|
||||
import {
|
||||
ActivityCreateDto,
|
||||
|
@ -26,8 +26,8 @@ export class ActivityService {
|
|||
this.access = AccessCore.create(accessRepository);
|
||||
}
|
||||
|
||||
async getAll(authUser: AuthUserDto, dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_READ, dto.albumId);
|
||||
async getAll(auth: AuthDto, dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_READ, dto.albumId);
|
||||
const activities = await this.repository.search({
|
||||
userId: dto.userId,
|
||||
albumId: dto.albumId,
|
||||
|
@ -38,16 +38,16 @@ export class ActivityService {
|
|||
return activities.map(mapActivity);
|
||||
}
|
||||
|
||||
async getStatistics(authUser: AuthUserDto, dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_READ, dto.albumId);
|
||||
async getStatistics(auth: AuthDto, dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_READ, dto.albumId);
|
||||
return { comments: await this.repository.getStatistics(dto.assetId, dto.albumId) };
|
||||
}
|
||||
|
||||
async create(authUser: AuthUserDto, dto: ActivityCreateDto): Promise<MaybeDuplicate<ActivityResponseDto>> {
|
||||
await this.access.requirePermission(authUser, Permission.ACTIVITY_CREATE, dto.albumId);
|
||||
async create(auth: AuthDto, dto: ActivityCreateDto): Promise<MaybeDuplicate<ActivityResponseDto>> {
|
||||
await this.access.requirePermission(auth, Permission.ACTIVITY_CREATE, dto.albumId);
|
||||
|
||||
const common = {
|
||||
userId: authUser.id,
|
||||
userId: auth.user.id,
|
||||
assetId: dto.assetId,
|
||||
albumId: dto.albumId,
|
||||
};
|
||||
|
@ -77,8 +77,8 @@ export class ActivityService {
|
|||
return { duplicate, value: mapActivity(activity) };
|
||||
}
|
||||
|
||||
async delete(authUser: AuthUserDto, id: string): Promise<void> {
|
||||
await this.access.requirePermission(authUser, Permission.ACTIVITY_DELETE, id);
|
||||
async delete(auth: AuthDto, id: string): Promise<void> {
|
||||
await this.access.requirePermission(auth, Permission.ACTIVITY_DELETE, id);
|
||||
await this.repository.delete(id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,9 +48,9 @@ describe(AlbumService.name, () => {
|
|||
notShared: 0,
|
||||
});
|
||||
|
||||
expect(albumMock.getOwned).toHaveBeenCalledWith(authStub.admin.id);
|
||||
expect(albumMock.getShared).toHaveBeenCalledWith(authStub.admin.id);
|
||||
expect(albumMock.getNotShared).toHaveBeenCalledWith(authStub.admin.id);
|
||||
expect(albumMock.getOwned).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
expect(albumMock.getShared).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
expect(albumMock.getNotShared).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -188,7 +188,7 @@ describe(AlbumService.name, () => {
|
|||
});
|
||||
|
||||
expect(albumMock.create).toHaveBeenCalledWith({
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
albumName: albumStub.empty.albumName,
|
||||
description: albumStub.empty.description,
|
||||
sharedUsers: [{ id: 'user-id' }],
|
||||
|
@ -312,7 +312,7 @@ describe(AlbumService.name, () => {
|
|||
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.sharedWithAdmin.id]));
|
||||
albumMock.getById.mockResolvedValue(albumStub.sharedWithAdmin);
|
||||
await expect(
|
||||
sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { sharedUserIds: [authStub.admin.id] }),
|
||||
sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { sharedUserIds: [authStub.admin.user.id] }),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(albumMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -332,11 +332,11 @@ describe(AlbumService.name, () => {
|
|||
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.sharedWithAdmin));
|
||||
albumMock.update.mockResolvedValue(albumStub.sharedWithAdmin);
|
||||
userMock.get.mockResolvedValue(userStub.user2);
|
||||
await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { sharedUserIds: [authStub.user2.id] });
|
||||
await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { sharedUserIds: [authStub.user2.user.id] });
|
||||
expect(albumMock.update).toHaveBeenCalledWith({
|
||||
id: albumStub.sharedWithAdmin.id,
|
||||
updatedAt: expect.any(Date),
|
||||
sharedUsers: [userStub.admin, { id: authStub.user2.id }],
|
||||
sharedUsers: [userStub.admin, { id: authStub.user2.user.id }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -370,12 +370,12 @@ describe(AlbumService.name, () => {
|
|||
albumMock.getById.mockResolvedValue(albumStub.sharedWithMultiple);
|
||||
|
||||
await expect(
|
||||
sut.removeUser(authStub.user1, albumStub.sharedWithMultiple.id, authStub.user2.id),
|
||||
sut.removeUser(authStub.user1, albumStub.sharedWithMultiple.id, authStub.user2.user.id),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(albumMock.update).not.toHaveBeenCalled();
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(
|
||||
authStub.user1.id,
|
||||
authStub.user1.user.id,
|
||||
new Set([albumStub.sharedWithMultiple.id]),
|
||||
);
|
||||
});
|
||||
|
@ -383,7 +383,7 @@ describe(AlbumService.name, () => {
|
|||
it('should allow a shared user to remove themselves', async () => {
|
||||
albumMock.getById.mockResolvedValue(albumStub.sharedWithUser);
|
||||
|
||||
await sut.removeUser(authStub.user1, albumStub.sharedWithUser.id, authStub.user1.id);
|
||||
await sut.removeUser(authStub.user1, albumStub.sharedWithUser.id, authStub.user1.user.id);
|
||||
|
||||
expect(albumMock.update).toHaveBeenCalledTimes(1);
|
||||
expect(albumMock.update).toHaveBeenCalledWith({
|
||||
|
@ -409,7 +409,7 @@ describe(AlbumService.name, () => {
|
|||
it('should not allow the owner to be removed', async () => {
|
||||
albumMock.getById.mockResolvedValue(albumStub.empty);
|
||||
|
||||
await expect(sut.removeUser(authStub.admin, albumStub.empty.id, authStub.admin.id)).rejects.toBeInstanceOf(
|
||||
await expect(sut.removeUser(authStub.admin, albumStub.empty.id, authStub.admin.user.id)).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
|
||||
|
@ -444,7 +444,7 @@ describe(AlbumService.name, () => {
|
|||
|
||||
expect(albumMock.getById).toHaveBeenCalledWith(albumStub.oneAsset.id, { withAssets: true });
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.id,
|
||||
authStub.admin.user.id,
|
||||
new Set([albumStub.oneAsset.id]),
|
||||
);
|
||||
});
|
||||
|
@ -465,7 +465,7 @@ describe(AlbumService.name, () => {
|
|||
|
||||
expect(albumMock.getById).toHaveBeenCalledWith('album-123', { withAssets: true });
|
||||
expect(accessMock.album.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||
authStub.adminSharedLink.sharedLinkId,
|
||||
authStub.adminSharedLink.sharedLink?.id,
|
||||
new Set(['album-123']),
|
||||
);
|
||||
});
|
||||
|
@ -485,14 +485,20 @@ describe(AlbumService.name, () => {
|
|||
await sut.get(authStub.user1, 'album-123', {});
|
||||
|
||||
expect(albumMock.getById).toHaveBeenCalledWith('album-123', { withAssets: true });
|
||||
expect(accessMock.album.checkSharedAlbumAccess).toHaveBeenCalledWith(authStub.user1.id, new Set(['album-123']));
|
||||
expect(accessMock.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
||||
authStub.user1.user.id,
|
||||
new Set(['album-123']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error for no access', async () => {
|
||||
await expect(sut.get(authStub.admin, 'album-123', {})).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['album-123']));
|
||||
expect(accessMock.album.checkSharedAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['album-123']));
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-123']));
|
||||
expect(accessMock.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.user.id,
|
||||
new Set(['album-123']),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -590,7 +596,7 @@ describe(AlbumService.name, () => {
|
|||
});
|
||||
|
||||
expect(accessMock.album.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||
authStub.adminSharedLink.sharedLinkId,
|
||||
authStub.adminSharedLink.sharedLink?.id,
|
||||
new Set(['album-123']),
|
||||
);
|
||||
});
|
||||
|
@ -610,7 +616,7 @@ describe(AlbumService.name, () => {
|
|||
updatedAt: expect.any(Date),
|
||||
albumThumbnailAssetId: 'asset-1',
|
||||
});
|
||||
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1']));
|
||||
});
|
||||
|
||||
it('should skip duplicate assets', async () => {
|
||||
|
@ -635,8 +641,8 @@ describe(AlbumService.name, () => {
|
|||
{ success: false, id: 'asset-1', error: BulkIdErrorReason.NO_PERMISSION },
|
||||
]);
|
||||
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1']));
|
||||
});
|
||||
|
||||
it('should not allow unauthorized access to the album', async () => {
|
||||
|
@ -729,7 +735,7 @@ describe(AlbumService.name, () => {
|
|||
|
||||
// // await expect(
|
||||
// // sut.removeAssetsFromAlbum(
|
||||
// // authUser,
|
||||
// // auth,
|
||||
// // {
|
||||
// // ids: ['1'],
|
||||
// // },
|
||||
|
@ -755,6 +761,6 @@ describe(AlbumService.name, () => {
|
|||
// albumRepositoryMock.get.mockImplementation(() => Promise.resolve<AlbumEntity>(albumEntity));
|
||||
// albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve<AddAssetsResponseDto>(albumResponse));
|
||||
|
||||
// await expect(sut.removeAssets(authUser, albumId, { ids: ['1'] })).rejects.toBeInstanceOf(ForbiddenException);
|
||||
// await expect(sut.removeAssets(auth, albumId, { ids: ['1'] })).rejects.toBeInstanceOf(ForbiddenException);
|
||||
// });
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra/entities';
|
|||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from '../asset';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { setUnion } from '../domain.util';
|
||||
import {
|
||||
AlbumAssetCount,
|
||||
|
@ -35,11 +35,11 @@ export class AlbumService {
|
|||
this.access = AccessCore.create(accessRepository);
|
||||
}
|
||||
|
||||
async getCount(authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||
async getCount(auth: AuthDto): Promise<AlbumCountResponseDto> {
|
||||
const [owned, shared, notShared] = await Promise.all([
|
||||
this.albumRepository.getOwned(authUser.id),
|
||||
this.albumRepository.getShared(authUser.id),
|
||||
this.albumRepository.getNotShared(authUser.id),
|
||||
this.albumRepository.getOwned(auth.user.id),
|
||||
this.albumRepository.getShared(auth.user.id),
|
||||
this.albumRepository.getNotShared(auth.user.id),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@ -49,7 +49,7 @@ export class AlbumService {
|
|||
};
|
||||
}
|
||||
|
||||
async getAll({ id: ownerId }: AuthUserDto, { assetId, shared }: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||
async getAll({ user: { id: ownerId } }: AuthDto, { assetId, shared }: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||
const invalidAlbumIds = await this.albumRepository.getInvalidThumbnail();
|
||||
for (const albumId of invalidAlbumIds) {
|
||||
const newThumbnail = await this.assetRepository.getFirstAssetForAlbumId(albumId);
|
||||
|
@ -98,8 +98,8 @@ export class AlbumService {
|
|||
);
|
||||
}
|
||||
|
||||
async get(authUser: AuthUserDto, id: string, dto: AlbumInfoDto): Promise<AlbumResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_READ, id);
|
||||
async get(auth: AuthDto, id: string, dto: AlbumInfoDto): Promise<AlbumResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_READ, id);
|
||||
await this.albumRepository.updateThumbnails();
|
||||
const withAssets = dto.withoutAssets === undefined ? true : !dto.withoutAssets;
|
||||
const album = await this.findOrFail(id, { withAssets });
|
||||
|
@ -113,7 +113,7 @@ export class AlbumService {
|
|||
};
|
||||
}
|
||||
|
||||
async create(authUser: AuthUserDto, dto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
||||
async create(auth: AuthDto, dto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
||||
for (const userId of dto.sharedWithUserIds || []) {
|
||||
const exists = await this.userRepository.get(userId, {});
|
||||
if (!exists) {
|
||||
|
@ -122,7 +122,7 @@ export class AlbumService {
|
|||
}
|
||||
|
||||
const album = await this.albumRepository.create({
|
||||
ownerId: authUser.id,
|
||||
ownerId: auth.user.id,
|
||||
albumName: dto.albumName,
|
||||
description: dto.description,
|
||||
sharedUsers: dto.sharedWithUserIds?.map((value) => ({ id: value }) as UserEntity) ?? [],
|
||||
|
@ -133,8 +133,8 @@ export class AlbumService {
|
|||
return mapAlbumWithAssets(album);
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_UPDATE, id);
|
||||
async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_UPDATE, id);
|
||||
|
||||
const album = await this.findOrFail(id, { withAssets: true });
|
||||
|
||||
|
@ -155,22 +155,22 @@ export class AlbumService {
|
|||
return mapAlbumWithoutAssets(updatedAlbum);
|
||||
}
|
||||
|
||||
async delete(authUser: AuthUserDto, id: string): Promise<void> {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_DELETE, id);
|
||||
async delete(auth: AuthDto, id: string): Promise<void> {
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_DELETE, id);
|
||||
|
||||
const album = await this.findOrFail(id, { withAssets: false });
|
||||
|
||||
await this.albumRepository.delete(album);
|
||||
}
|
||||
|
||||
async addAssets(authUser: AuthUserDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||
const album = await this.findOrFail(id, { withAssets: false });
|
||||
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_READ, id);
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_READ, id);
|
||||
|
||||
const existingAssetIds = await this.albumRepository.getAssetIds(id, dto.ids);
|
||||
const notPresentAssetIds = dto.ids.filter((id) => !existingAssetIds.has(id));
|
||||
const allowedAssetIds = await this.access.checkAccess(authUser, Permission.ASSET_SHARE, notPresentAssetIds);
|
||||
const allowedAssetIds = await this.access.checkAccess(auth, Permission.ASSET_SHARE, notPresentAssetIds);
|
||||
|
||||
const results: BulkIdResponseDto[] = [];
|
||||
for (const assetId of dto.ids) {
|
||||
|
@ -202,14 +202,14 @@ export class AlbumService {
|
|||
return results;
|
||||
}
|
||||
|
||||
async removeAssets(authUser: AuthUserDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||
const album = await this.findOrFail(id, { withAssets: false });
|
||||
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_READ, id);
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_READ, id);
|
||||
|
||||
const existingAssetIds = await this.albumRepository.getAssetIds(id, dto.ids);
|
||||
const canRemove = await this.access.checkAccess(authUser, Permission.ALBUM_REMOVE_ASSET, existingAssetIds);
|
||||
const canShare = await this.access.checkAccess(authUser, Permission.ASSET_SHARE, existingAssetIds);
|
||||
const canRemove = await this.access.checkAccess(auth, Permission.ALBUM_REMOVE_ASSET, existingAssetIds);
|
||||
const canShare = await this.access.checkAccess(auth, Permission.ASSET_SHARE, existingAssetIds);
|
||||
const allowedAssetIds = setUnion(canRemove, canShare);
|
||||
|
||||
const results: BulkIdResponseDto[] = [];
|
||||
|
@ -241,8 +241,8 @@ export class AlbumService {
|
|||
return results;
|
||||
}
|
||||
|
||||
async addUsers(authUser: AuthUserDto, id: string, dto: AddUsersDto): Promise<AlbumResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_SHARE, id);
|
||||
async addUsers(auth: AuthDto, id: string, dto: AddUsersDto): Promise<AlbumResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_SHARE, id);
|
||||
|
||||
const album = await this.findOrFail(id, { withAssets: false });
|
||||
|
||||
|
@ -273,9 +273,9 @@ export class AlbumService {
|
|||
.then(mapAlbumWithoutAssets);
|
||||
}
|
||||
|
||||
async removeUser(authUser: AuthUserDto, id: string, userId: string | 'me'): Promise<void> {
|
||||
async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {
|
||||
if (userId === 'me') {
|
||||
userId = authUser.id;
|
||||
userId = auth.user.id;
|
||||
}
|
||||
|
||||
const album = await this.findOrFail(id, { withAssets: false });
|
||||
|
@ -290,8 +290,8 @@ export class AlbumService {
|
|||
}
|
||||
|
||||
// non-admin can remove themselves
|
||||
if (authUser.id !== userId) {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_SHARE, id);
|
||||
if (auth.user.id !== userId) {
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_SHARE, id);
|
||||
}
|
||||
|
||||
await this.albumRepository.update({
|
||||
|
|
|
@ -21,7 +21,7 @@ describe(APIKeyService.name, () => {
|
|||
expect(keyMock.create).toHaveBeenCalledWith({
|
||||
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
||||
name: 'Test Key',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
});
|
||||
expect(cryptoMock.randomBytes).toHaveBeenCalled();
|
||||
expect(cryptoMock.hashSha256).toHaveBeenCalled();
|
||||
|
@ -35,7 +35,7 @@ describe(APIKeyService.name, () => {
|
|||
expect(keyMock.create).toHaveBeenCalledWith({
|
||||
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
||||
name: 'API Key',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
});
|
||||
expect(cryptoMock.randomBytes).toHaveBeenCalled();
|
||||
expect(cryptoMock.hashSha256).toHaveBeenCalled();
|
||||
|
@ -59,7 +59,7 @@ describe(APIKeyService.name, () => {
|
|||
|
||||
await sut.update(authStub.admin, 'random-guid', { name: 'New Name' });
|
||||
|
||||
expect(keyMock.update).toHaveBeenCalledWith(authStub.admin.id, 'random-guid', { name: 'New Name' });
|
||||
expect(keyMock.update).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid', { name: 'New Name' });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -77,7 +77,7 @@ describe(APIKeyService.name, () => {
|
|||
|
||||
await sut.delete(authStub.admin, 'random-guid');
|
||||
|
||||
expect(keyMock.delete).toHaveBeenCalledWith(authStub.admin.id, 'random-guid');
|
||||
expect(keyMock.delete).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -87,7 +87,7 @@ describe(APIKeyService.name, () => {
|
|||
|
||||
await expect(sut.getById(authStub.admin, 'random-guid')).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'random-guid');
|
||||
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid');
|
||||
});
|
||||
|
||||
it('should get a key by id', async () => {
|
||||
|
@ -95,7 +95,7 @@ describe(APIKeyService.name, () => {
|
|||
|
||||
await sut.getById(authStub.admin, 'random-guid');
|
||||
|
||||
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'random-guid');
|
||||
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'random-guid');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -105,7 +105,7 @@ describe(APIKeyService.name, () => {
|
|||
|
||||
await expect(sut.getAll(authStub.admin)).resolves.toHaveLength(1);
|
||||
|
||||
expect(keyMock.getByUserId).toHaveBeenCalledWith(authStub.admin.id);
|
||||
expect(keyMock.getByUserId).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { APIKeyEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { ICryptoRepository, IKeyRepository } from '../repositories';
|
||||
import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto } from './api-key.dto';
|
||||
|
||||
|
@ -11,47 +11,47 @@ export class APIKeyService {
|
|||
@Inject(IKeyRepository) private repository: IKeyRepository,
|
||||
) {}
|
||||
|
||||
async create(authUser: AuthUserDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||
async create(auth: AuthDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||
const secret = this.crypto.randomBytes(32).toString('base64').replace(/\W/g, '');
|
||||
const entity = await this.repository.create({
|
||||
key: this.crypto.hashSha256(secret),
|
||||
name: dto.name || 'API Key',
|
||||
userId: authUser.id,
|
||||
userId: auth.user.id,
|
||||
});
|
||||
|
||||
return { secret, apiKey: this.map(entity) };
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: string, dto: APIKeyCreateDto): Promise<APIKeyResponseDto> {
|
||||
const exists = await this.repository.getById(authUser.id, id);
|
||||
async update(auth: AuthDto, id: string, dto: APIKeyCreateDto): Promise<APIKeyResponseDto> {
|
||||
const exists = await this.repository.getById(auth.user.id, id);
|
||||
if (!exists) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
|
||||
const key = await this.repository.update(authUser.id, id, { name: dto.name });
|
||||
const key = await this.repository.update(auth.user.id, id, { name: dto.name });
|
||||
|
||||
return this.map(key);
|
||||
}
|
||||
|
||||
async delete(authUser: AuthUserDto, id: string): Promise<void> {
|
||||
const exists = await this.repository.getById(authUser.id, id);
|
||||
async delete(auth: AuthDto, id: string): Promise<void> {
|
||||
const exists = await this.repository.getById(auth.user.id, id);
|
||||
if (!exists) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
|
||||
await this.repository.delete(authUser.id, id);
|
||||
await this.repository.delete(auth.user.id, id);
|
||||
}
|
||||
|
||||
async getById(authUser: AuthUserDto, id: string): Promise<APIKeyResponseDto> {
|
||||
const key = await this.repository.getById(authUser.id, id);
|
||||
async getById(auth: AuthDto, id: string): Promise<APIKeyResponseDto> {
|
||||
const key = await this.repository.getById(auth.user.id, id);
|
||||
if (!key) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
return this.map(key);
|
||||
}
|
||||
|
||||
async getAll(authUser: AuthUserDto): Promise<APIKeyResponseDto[]> {
|
||||
const keys = await this.repository.getByUserId(authUser.id);
|
||||
async getAll(auth: AuthDto): Promise<APIKeyResponseDto[]> {
|
||||
const keys = await this.repository.getByUserId(auth.user.id);
|
||||
return keys.map((key) => this.map(key));
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ const statResponse: AssetStatsResponseDto = {
|
|||
|
||||
const uploadFile = {
|
||||
nullAuth: {
|
||||
authUser: null,
|
||||
auth: null,
|
||||
fieldName: UploadFieldName.ASSET_DATA,
|
||||
file: {
|
||||
checksum: Buffer.from('checksum', 'utf8'),
|
||||
|
@ -69,7 +69,7 @@ const uploadFile = {
|
|||
},
|
||||
filename: (fieldName: UploadFieldName, filename: string) => {
|
||||
return {
|
||||
authUser: authStub.admin,
|
||||
auth: authStub.admin,
|
||||
fieldName,
|
||||
file: {
|
||||
mimeType: 'image/jpeg',
|
||||
|
@ -328,7 +328,7 @@ describe(AssetService.name, () => {
|
|||
{ title: '9 years since...', assets: [mapAsset(assetStub.imageFrom2015)] },
|
||||
]);
|
||||
|
||||
expect(assetMock.getByDayOfYear.mock.calls).toEqual([[authStub.admin.id, { day: 15, month: 1 }]]);
|
||||
expect(assetMock.getByDayOfYear.mock.calls).toEqual([[authStub.admin.user.id, { day: 15, month: 1 }]]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -341,7 +341,7 @@ describe(AssetService.name, () => {
|
|||
size: TimeBucketSize.DAY,
|
||||
}),
|
||||
).resolves.toEqual(expect.arrayContaining([{ timeBucket: 'bucket', count: 1 }]));
|
||||
expect(assetMock.getTimeBuckets).toBeCalledWith({ size: TimeBucketSize.DAY, userIds: [authStub.admin.id] });
|
||||
expect(assetMock.getTimeBuckets).toBeCalledWith({ size: TimeBucketSize.DAY, userIds: [authStub.admin.user.id] });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -354,7 +354,7 @@ describe(AssetService.name, () => {
|
|||
sut.getTimeBucket(authStub.admin, { size: TimeBucketSize.DAY, timeBucket: 'bucket', albumId: 'album-id' }),
|
||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
||||
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['album-id']));
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-id']));
|
||||
expect(assetMock.getTimeBucket).toBeCalledWith('bucket', {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
|
@ -370,14 +370,14 @@ describe(AssetService.name, () => {
|
|||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
||||
expect(assetMock.getTimeBucket).toBeCalledWith('bucket', {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
userIds: [authStub.admin.id],
|
||||
userIds: [authStub.admin.user.id],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -388,13 +388,13 @@ describe(AssetService.name, () => {
|
|||
sut.getTimeBucket(authStub.admin, {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
||||
expect(assetMock.getTimeBucket).toBeCalledWith('bucket', {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
userIds: [authStub.admin.id],
|
||||
userIds: [authStub.admin.user.id],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -405,7 +405,7 @@ describe(AssetService.name, () => {
|
|||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
withPartners: true,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
).rejects.toThrowError(BadRequestException);
|
||||
|
||||
|
@ -415,7 +415,7 @@ describe(AssetService.name, () => {
|
|||
timeBucket: 'bucket',
|
||||
isArchived: undefined,
|
||||
withPartners: true,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
).rejects.toThrowError(BadRequestException);
|
||||
});
|
||||
|
@ -427,7 +427,7 @@ describe(AssetService.name, () => {
|
|||
timeBucket: 'bucket',
|
||||
isFavorite: true,
|
||||
withPartners: true,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
).rejects.toThrowError(BadRequestException);
|
||||
|
||||
|
@ -437,7 +437,7 @@ describe(AssetService.name, () => {
|
|||
timeBucket: 'bucket',
|
||||
isFavorite: false,
|
||||
withPartners: true,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
).rejects.toThrowError(BadRequestException);
|
||||
});
|
||||
|
@ -449,7 +449,7 @@ describe(AssetService.name, () => {
|
|||
timeBucket: 'bucket',
|
||||
isTrashed: true,
|
||||
withPartners: true,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
}),
|
||||
).rejects.toThrowError(BadRequestException);
|
||||
});
|
||||
|
@ -459,9 +459,9 @@ describe(AssetService.name, () => {
|
|||
it('should require the asset.download permission', async () => {
|
||||
await expect(sut.downloadFile(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkAlbumAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1']));
|
||||
});
|
||||
|
||||
it('should throw an error if the asset is not found', async () => {
|
||||
|
@ -550,28 +550,28 @@ describe(AssetService.name, () => {
|
|||
|
||||
await expect(sut.getDownloadInfo(authStub.admin, { albumId: 'album-1' })).resolves.toEqual(downloadResponse);
|
||||
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['album-1']));
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-1']));
|
||||
expect(assetMock.getByAlbumId).toHaveBeenCalledWith({ take: 2500, skip: 0 }, 'album-1');
|
||||
});
|
||||
|
||||
it('should return a list of archives (userId)', async () => {
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.id]));
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.user.id]));
|
||||
assetMock.getByUserId.mockResolvedValue({
|
||||
items: [assetStub.image, assetStub.video],
|
||||
hasNextPage: false,
|
||||
});
|
||||
|
||||
await expect(sut.getDownloadInfo(authStub.admin, { userId: authStub.admin.id })).resolves.toEqual(
|
||||
await expect(sut.getDownloadInfo(authStub.admin, { userId: authStub.admin.user.id })).resolves.toEqual(
|
||||
downloadResponse,
|
||||
);
|
||||
|
||||
expect(assetMock.getByUserId).toHaveBeenCalledWith({ take: 2500, skip: 0 }, authStub.admin.id, {
|
||||
expect(assetMock.getByUserId).toHaveBeenCalledWith({ take: 2500, skip: 0 }, authStub.admin.user.id, {
|
||||
isVisible: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should split archives by size', async () => {
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.id]));
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.user.id]));
|
||||
|
||||
assetMock.getByUserId.mockResolvedValue({
|
||||
items: [
|
||||
|
@ -585,7 +585,7 @@ describe(AssetService.name, () => {
|
|||
|
||||
await expect(
|
||||
sut.getDownloadInfo(authStub.admin, {
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
archiveSize: 30_000,
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
|
@ -624,25 +624,25 @@ describe(AssetService.name, () => {
|
|||
it('should get the statistics for a user, excluding archived assets', async () => {
|
||||
assetMock.getStatistics.mockResolvedValue(stats);
|
||||
await expect(sut.getStatistics(authStub.admin, { isArchived: false })).resolves.toEqual(statResponse);
|
||||
expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, { isArchived: false });
|
||||
expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, { isArchived: false });
|
||||
});
|
||||
|
||||
it('should get the statistics for a user for archived assets', async () => {
|
||||
assetMock.getStatistics.mockResolvedValue(stats);
|
||||
await expect(sut.getStatistics(authStub.admin, { isArchived: true })).resolves.toEqual(statResponse);
|
||||
expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, { isArchived: true });
|
||||
expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, { isArchived: true });
|
||||
});
|
||||
|
||||
it('should get the statistics for a user for favorite assets', async () => {
|
||||
assetMock.getStatistics.mockResolvedValue(stats);
|
||||
await expect(sut.getStatistics(authStub.admin, { isFavorite: true })).resolves.toEqual(statResponse);
|
||||
expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, { isFavorite: true });
|
||||
expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, { isFavorite: true });
|
||||
});
|
||||
|
||||
it('should get the statistics for a user for all assets', async () => {
|
||||
assetMock.getStatistics.mockResolvedValue(stats);
|
||||
await expect(sut.getStatistics(authStub.admin, {})).resolves.toEqual(statResponse);
|
||||
expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, {});
|
||||
expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -762,7 +762,7 @@ describe(AssetService.name, () => {
|
|||
stackParentId: 'parent',
|
||||
});
|
||||
|
||||
expect(communicationMock.send).toHaveBeenCalledWith(CommunicationEvent.ASSET_UPDATE, authStub.user1.id, [
|
||||
expect(communicationMock.send).toHaveBeenCalledWith(CommunicationEvent.ASSET_UPDATE, authStub.user1.user.id, [
|
||||
'asset-1',
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { DateTime, Duration } from 'luxon';
|
|||
import { extname } from 'path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { mimeTypes } from '../domain.constant';
|
||||
import { HumanReadableSize, usePagination } from '../domain.util';
|
||||
import { IAssetDeletionJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
|
||||
|
@ -63,7 +63,7 @@ export enum UploadFieldName {
|
|||
}
|
||||
|
||||
export interface UploadRequest {
|
||||
authUser: AuthUserDto | null;
|
||||
auth: AuthDto | null;
|
||||
fieldName: UploadFieldName;
|
||||
file: UploadFile;
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ export class AssetService {
|
|||
this.configCore = SystemConfigCore.create(configRepository);
|
||||
}
|
||||
|
||||
search(authUser: AuthUserDto, dto: AssetSearchDto) {
|
||||
search(auth: AuthDto, dto: AssetSearchDto) {
|
||||
let checksum: Buffer | undefined = undefined;
|
||||
|
||||
if (dto.checksum) {
|
||||
|
@ -109,7 +109,7 @@ export class AssetService {
|
|||
...dto,
|
||||
order,
|
||||
checksum,
|
||||
ownerId: authUser.id,
|
||||
ownerId: auth.user.id,
|
||||
})
|
||||
.then((assets) =>
|
||||
assets.map((asset) =>
|
||||
|
@ -121,8 +121,8 @@ export class AssetService {
|
|||
);
|
||||
}
|
||||
|
||||
canUploadFile({ authUser, fieldName, file }: UploadRequest): true {
|
||||
this.access.requireUploadAccess(authUser);
|
||||
canUploadFile({ auth, fieldName, file }: UploadRequest): true {
|
||||
this.access.requireUploadAccess(auth);
|
||||
|
||||
const filename = file.originalName;
|
||||
|
||||
|
@ -156,8 +156,8 @@ export class AssetService {
|
|||
throw new BadRequestException(`Unsupported file type ${filename}`);
|
||||
}
|
||||
|
||||
getUploadFilename({ authUser, fieldName, file }: UploadRequest): string {
|
||||
this.access.requireUploadAccess(authUser);
|
||||
getUploadFilename({ auth, fieldName, file }: UploadRequest): string {
|
||||
this.access.requireUploadAccess(auth);
|
||||
|
||||
const originalExt = extname(file.originalName);
|
||||
|
||||
|
@ -171,12 +171,12 @@ export class AssetService {
|
|||
return sanitize(`${this.cryptoRepository.randomUUID()}${lookup[fieldName]}`);
|
||||
}
|
||||
|
||||
getUploadFolder({ authUser, fieldName }: UploadRequest): string {
|
||||
authUser = this.access.requireUploadAccess(authUser);
|
||||
getUploadFolder({ auth, fieldName }: UploadRequest): string {
|
||||
auth = this.access.requireUploadAccess(auth);
|
||||
|
||||
let folder = StorageCore.getFolderLocation(StorageFolder.UPLOAD, authUser.id);
|
||||
let folder = StorageCore.getFolderLocation(StorageFolder.UPLOAD, auth.user.id);
|
||||
if (fieldName === UploadFieldName.PROFILE_DATA) {
|
||||
folder = StorageCore.getFolderLocation(StorageFolder.PROFILE, authUser.id);
|
||||
folder = StorageCore.getFolderLocation(StorageFolder.PROFILE, auth.user.id);
|
||||
}
|
||||
|
||||
this.storageRepository.mkdirSync(folder);
|
||||
|
@ -184,13 +184,13 @@ export class AssetService {
|
|||
return folder;
|
||||
}
|
||||
|
||||
getMapMarkers(authUser: AuthUserDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
||||
return this.assetRepository.getMapMarkers(authUser.id, options);
|
||||
getMapMarkers(auth: AuthDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
||||
return this.assetRepository.getMapMarkers(auth.user.id, options);
|
||||
}
|
||||
|
||||
async getMemoryLane(authUser: AuthUserDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
|
||||
async getMemoryLane(auth: AuthDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const assets = await this.assetRepository.getByDayOfYear(authUser.id, dto);
|
||||
const assets = await this.assetRepository.getByDayOfYear(auth.user.id, dto);
|
||||
|
||||
return _.chain(assets)
|
||||
.filter((asset) => asset.localDateTime.getFullYear() < currentYear)
|
||||
|
@ -207,17 +207,17 @@ export class AssetService {
|
|||
.value();
|
||||
}
|
||||
|
||||
private async timeBucketChecks(authUser: AuthUserDto, dto: TimeBucketDto) {
|
||||
private async timeBucketChecks(auth: AuthDto, dto: TimeBucketDto) {
|
||||
if (dto.albumId) {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_READ, [dto.albumId]);
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_READ, [dto.albumId]);
|
||||
} else {
|
||||
dto.userId = dto.userId || authUser.id;
|
||||
dto.userId = dto.userId || auth.user.id;
|
||||
}
|
||||
|
||||
if (dto.userId) {
|
||||
await this.access.requirePermission(authUser, Permission.TIMELINE_READ, [dto.userId]);
|
||||
await this.access.requirePermission(auth, Permission.TIMELINE_READ, [dto.userId]);
|
||||
if (dto.isArchived !== false) {
|
||||
await this.access.requirePermission(authUser, Permission.ARCHIVE_READ, [dto.userId]);
|
||||
await this.access.requirePermission(auth, Permission.ARCHIVE_READ, [dto.userId]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,28 +234,28 @@ export class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
async getTimeBuckets(authUser: AuthUserDto, dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
|
||||
await this.timeBucketChecks(authUser, dto);
|
||||
const timeBucketOptions = await this.buildTimeBucketOptions(authUser, dto);
|
||||
async getTimeBuckets(auth: AuthDto, dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
|
||||
await this.timeBucketChecks(auth, dto);
|
||||
const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto);
|
||||
|
||||
return this.assetRepository.getTimeBuckets(timeBucketOptions);
|
||||
}
|
||||
|
||||
async getTimeBucket(
|
||||
authUser: AuthUserDto,
|
||||
auth: AuthDto,
|
||||
dto: TimeBucketAssetDto,
|
||||
): Promise<AssetResponseDto[] | SanitizedAssetResponseDto[]> {
|
||||
await this.timeBucketChecks(authUser, dto);
|
||||
const timeBucketOptions = await this.buildTimeBucketOptions(authUser, dto);
|
||||
await this.timeBucketChecks(auth, dto);
|
||||
const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto);
|
||||
const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions);
|
||||
if (authUser.isShowMetadata) {
|
||||
if (!auth.sharedLink || auth.sharedLink?.showExif) {
|
||||
return assets.map((asset) => mapAsset(asset, { withStack: true }));
|
||||
} else {
|
||||
return assets.map((asset) => mapAsset(asset, { stripMetadata: true }));
|
||||
}
|
||||
}
|
||||
|
||||
async buildTimeBucketOptions(authUser: AuthUserDto, dto: TimeBucketDto): Promise<TimeBucketOptions> {
|
||||
async buildTimeBucketOptions(auth: AuthDto, dto: TimeBucketDto): Promise<TimeBucketOptions> {
|
||||
const { userId, ...options } = dto;
|
||||
let userIds: string[] | undefined = undefined;
|
||||
|
||||
|
@ -263,7 +263,7 @@ export class AssetService {
|
|||
userIds = [userId];
|
||||
|
||||
if (dto.withPartners) {
|
||||
const partners = await this.partnerRepository.getAll(authUser.id);
|
||||
const partners = await this.partnerRepository.getAll(auth.user.id);
|
||||
const partnersIds = partners
|
||||
.filter((partner) => partner.sharedBy && partner.sharedWith && partner.inTimeline)
|
||||
.map((partner) => partner.sharedById);
|
||||
|
@ -274,8 +274,8 @@ export class AssetService {
|
|||
|
||||
return { ...options, userIds };
|
||||
}
|
||||
async downloadFile(authUser: AuthUserDto, id: string): Promise<ImmichReadStream> {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_DOWNLOAD, id);
|
||||
async downloadFile(auth: AuthDto, id: string): Promise<ImmichReadStream> {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_DOWNLOAD, id);
|
||||
|
||||
const [asset] = await this.assetRepository.getByIds([id]);
|
||||
if (!asset) {
|
||||
|
@ -289,12 +289,12 @@ export class AssetService {
|
|||
return this.storageRepository.createReadStream(asset.originalPath, mimeTypes.lookup(asset.originalPath));
|
||||
}
|
||||
|
||||
async getDownloadInfo(authUser: AuthUserDto, dto: DownloadInfoDto): Promise<DownloadResponseDto> {
|
||||
async getDownloadInfo(auth: AuthDto, dto: DownloadInfoDto): Promise<DownloadResponseDto> {
|
||||
const targetSize = dto.archiveSize || HumanReadableSize.GiB * 4;
|
||||
const archives: DownloadArchiveInfo[] = [];
|
||||
let archive: DownloadArchiveInfo = { size: 0, assetIds: [] };
|
||||
|
||||
const assetPagination = await this.getDownloadAssets(authUser, dto);
|
||||
const assetPagination = await this.getDownloadAssets(auth, dto);
|
||||
for await (const assets of assetPagination) {
|
||||
// motion part of live photos
|
||||
const motionIds = assets.map((asset) => asset.livePhotoVideoId).filter<string>((id): id is string => !!id);
|
||||
|
@ -323,8 +323,8 @@ export class AssetService {
|
|||
};
|
||||
}
|
||||
|
||||
async downloadArchive(authUser: AuthUserDto, dto: AssetIdsDto): Promise<ImmichReadStream> {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_DOWNLOAD, dto.assetIds);
|
||||
async downloadArchive(auth: AuthDto, dto: AssetIdsDto): Promise<ImmichReadStream> {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_DOWNLOAD, dto.assetIds);
|
||||
|
||||
const zip = this.storageRepository.createZipStream();
|
||||
const assets = await this.assetRepository.getByIds(dto.assetIds);
|
||||
|
@ -347,12 +347,12 @@ export class AssetService {
|
|||
return { stream: zip.stream };
|
||||
}
|
||||
|
||||
private async getDownloadAssets(authUser: AuthUserDto, dto: DownloadInfoDto): Promise<AsyncGenerator<AssetEntity[]>> {
|
||||
private async getDownloadAssets(auth: AuthDto, dto: DownloadInfoDto): Promise<AsyncGenerator<AssetEntity[]>> {
|
||||
const PAGINATION_SIZE = 2500;
|
||||
|
||||
if (dto.assetIds) {
|
||||
const assetIds = dto.assetIds;
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_DOWNLOAD, assetIds);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_DOWNLOAD, assetIds);
|
||||
const assets = await this.assetRepository.getByIds(assetIds);
|
||||
return (async function* () {
|
||||
yield assets;
|
||||
|
@ -361,13 +361,13 @@ export class AssetService {
|
|||
|
||||
if (dto.albumId) {
|
||||
const albumId = dto.albumId;
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_DOWNLOAD, albumId);
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_DOWNLOAD, albumId);
|
||||
return usePagination(PAGINATION_SIZE, (pagination) => this.assetRepository.getByAlbumId(pagination, albumId));
|
||||
}
|
||||
|
||||
if (dto.userId) {
|
||||
const userId = dto.userId;
|
||||
await this.access.requirePermission(authUser, Permission.TIMELINE_DOWNLOAD, userId);
|
||||
await this.access.requirePermission(auth, Permission.TIMELINE_DOWNLOAD, userId);
|
||||
return usePagination(PAGINATION_SIZE, (pagination) =>
|
||||
this.assetRepository.getByUserId(pagination, userId, { isVisible: true }),
|
||||
);
|
||||
|
@ -376,22 +376,22 @@ export class AssetService {
|
|||
throw new BadRequestException('assetIds, albumId, or userId is required');
|
||||
}
|
||||
|
||||
async getStatistics(authUser: AuthUserDto, dto: AssetStatsDto) {
|
||||
const stats = await this.assetRepository.getStatistics(authUser.id, dto);
|
||||
async getStatistics(auth: AuthDto, dto: AssetStatsDto) {
|
||||
const stats = await this.assetRepository.getStatistics(auth.user.id, dto);
|
||||
return mapStats(stats);
|
||||
}
|
||||
|
||||
async getRandom(authUser: AuthUserDto, count: number): Promise<AssetResponseDto[]> {
|
||||
const assets = await this.assetRepository.getRandom(authUser.id, count);
|
||||
async getRandom(auth: AuthDto, count: number): Promise<AssetResponseDto[]> {
|
||||
const assets = await this.assetRepository.getRandom(auth.user.id, count);
|
||||
return assets.map((a) => mapAsset(a));
|
||||
}
|
||||
|
||||
async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
||||
return this.assetRepository.getAllByDeviceId(authUser.id, deviceId);
|
||||
async getUserAssetsByDeviceId(auth: AuthDto, deviceId: string) {
|
||||
return this.assetRepository.getAllByDeviceId(auth.user.id, deviceId);
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
|
||||
async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, id);
|
||||
|
||||
const { description, dateTimeOriginal, latitude, longitude, ...rest } = dto;
|
||||
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude });
|
||||
|
@ -400,9 +400,9 @@ export class AssetService {
|
|||
return mapAsset(asset);
|
||||
}
|
||||
|
||||
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto): Promise<void> {
|
||||
async updateAll(auth: AuthDto, dto: AssetBulkUpdateDto): Promise<void> {
|
||||
const { ids, removeParent, dateTimeOriginal, latitude, longitude, ...options } = dto;
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, ids);
|
||||
|
||||
if (removeParent) {
|
||||
(options as Partial<AssetEntity>).stackParentId = null;
|
||||
|
@ -411,7 +411,7 @@ export class AssetService {
|
|||
// All the unique parent's -> parent is set to null
|
||||
ids.push(...new Set(assets.filter((a) => !!a.stackParentId).map((a) => a.stackParentId!)));
|
||||
} else if (options.stackParentId) {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, options.stackParentId);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, options.stackParentId);
|
||||
// Merge stacks
|
||||
const assets = await this.assetRepository.getByIds(ids);
|
||||
const assetsWithChildren = assets.filter((a) => a.stack && a.stack.length > 0);
|
||||
|
@ -430,7 +430,7 @@ export class AssetService {
|
|||
}
|
||||
|
||||
await this.assetRepository.updateAll(ids, options);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_UPDATE, authUser.id, ids);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_UPDATE, auth.user.id, ids);
|
||||
}
|
||||
|
||||
async handleAssetDeletionCheck() {
|
||||
|
@ -493,10 +493,10 @@ export class AssetService {
|
|||
return true;
|
||||
}
|
||||
|
||||
async deleteAll(authUser: AuthUserDto, dto: AssetBulkDeleteDto): Promise<void> {
|
||||
async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise<void> {
|
||||
const { ids, force } = dto;
|
||||
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_DELETE, ids);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_DELETE, ids);
|
||||
|
||||
if (force) {
|
||||
for (const id of ids) {
|
||||
|
@ -504,20 +504,20 @@ export class AssetService {
|
|||
}
|
||||
} else {
|
||||
await this.assetRepository.softDeleteAll(ids);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_TRASH, authUser.id, ids);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_TRASH, auth.user.id, ids);
|
||||
}
|
||||
}
|
||||
|
||||
async handleTrashAction(authUser: AuthUserDto, action: TrashAction): Promise<void> {
|
||||
async handleTrashAction(auth: AuthDto, action: TrashAction): Promise<void> {
|
||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
|
||||
this.assetRepository.getByUserId(pagination, authUser.id, { trashedBefore: DateTime.now().toJSDate() }),
|
||||
this.assetRepository.getByUserId(pagination, auth.user.id, { trashedBefore: DateTime.now().toJSDate() }),
|
||||
);
|
||||
|
||||
if (action == TrashAction.RESTORE_ALL) {
|
||||
for await (const assets of assetPagination) {
|
||||
const ids = assets.map((a) => a.id);
|
||||
await this.assetRepository.restoreAll(ids);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_RESTORE, authUser.id, ids);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_RESTORE, auth.user.id, ids);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -532,17 +532,17 @@ export class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
async restoreAll(authUser: AuthUserDto, dto: BulkIdsDto): Promise<void> {
|
||||
async restoreAll(auth: AuthDto, dto: BulkIdsDto): Promise<void> {
|
||||
const { ids } = dto;
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_RESTORE, ids);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_RESTORE, ids);
|
||||
await this.assetRepository.restoreAll(ids);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_RESTORE, authUser.id, ids);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_RESTORE, auth.user.id, ids);
|
||||
}
|
||||
|
||||
async updateStackParent(authUser: AuthUserDto, dto: UpdateStackParentDto): Promise<void> {
|
||||
async updateStackParent(auth: AuthDto, dto: UpdateStackParentDto): Promise<void> {
|
||||
const { oldParentId, newParentId } = dto;
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_READ, oldParentId);
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, newParentId);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_READ, oldParentId);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, newParentId);
|
||||
|
||||
const childIds: string[] = [];
|
||||
const oldParent = await this.assetRepository.getById(oldParentId);
|
||||
|
@ -552,14 +552,14 @@ export class AssetService {
|
|||
childIds.push(...(oldParent.stack?.map((a) => a.id) ?? []));
|
||||
}
|
||||
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_UPDATE, authUser.id, [...childIds, newParentId]);
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_UPDATE, auth.user.id, [...childIds, newParentId]);
|
||||
await this.assetRepository.updateAll(childIds, { stackParentId: newParentId });
|
||||
// Remove ParentId of new parent if this was previously a child of some other asset
|
||||
return this.assetRepository.updateAll([newParentId], { stackParentId: null });
|
||||
}
|
||||
|
||||
async run(authUser: AuthUserDto, dto: AssetJobsDto) {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, dto.assetIds);
|
||||
async run(auth: AuthDto, dto: AssetJobsDto) {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, dto.assetIds);
|
||||
|
||||
for (const id of dto.assetIds) {
|
||||
switch (dto.name) {
|
||||
|
|
|
@ -65,7 +65,7 @@ describe(AuditService.name, () => {
|
|||
|
||||
expect(auditMock.getAfter).toHaveBeenCalledWith(date, {
|
||||
action: DatabaseAction.DELETE,
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
entityType: EntityType.ASSET,
|
||||
});
|
||||
});
|
||||
|
@ -81,7 +81,7 @@ describe(AuditService.name, () => {
|
|||
|
||||
expect(auditMock.getAfter).toHaveBeenCalledWith(date, {
|
||||
action: DatabaseAction.DELETE,
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
entityType: EntityType.ASSET,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'
|
|||
import { DateTime } from 'luxon';
|
||||
import { resolve } from 'node:path';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { AUDIT_LOG_MAX_DURATION } from '../domain.constant';
|
||||
import { usePagination } from '../domain.util';
|
||||
import { JOBS_ASSET_PAGINATION_SIZE } from '../job';
|
||||
|
@ -48,9 +48,9 @@ export class AuditService {
|
|||
return true;
|
||||
}
|
||||
|
||||
async getDeletes(authUser: AuthUserDto, dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> {
|
||||
const userId = dto.userId || authUser.id;
|
||||
await this.access.requirePermission(authUser, Permission.TIMELINE_READ, userId);
|
||||
async getDeletes(auth: AuthDto, dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> {
|
||||
const userId = dto.userId || auth.user.id;
|
||||
await this.access.requirePermission(auth, Permission.TIMELINE_READ, userId);
|
||||
|
||||
const audits = await this.repository.getAfter(dto.after, {
|
||||
ownerId: userId,
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import { UserEntity, UserTokenEntity } from '@app/infra/entities';
|
||||
import { APIKeyEntity, SharedLinkEntity, UserEntity, UserTokenEntity } from '@app/infra/entities';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
|
||||
|
||||
export class AuthUserDto {
|
||||
id!: string;
|
||||
email!: string;
|
||||
isAdmin!: boolean;
|
||||
isPublicUser?: boolean;
|
||||
sharedLinkId?: string;
|
||||
isAllowUpload?: boolean;
|
||||
isAllowDownload?: boolean;
|
||||
isShowMetadata?: boolean;
|
||||
accessTokenId?: string;
|
||||
externalPath?: string | null;
|
||||
export class AuthDto {
|
||||
user!: UserEntity;
|
||||
|
||||
apiKey?: APIKeyEntity;
|
||||
sharedLink?: SharedLinkEntity;
|
||||
userToken?: UserTokenEntity;
|
||||
}
|
||||
|
||||
export class LoginCredentialDto {
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
IUserTokenRepository,
|
||||
} from '../repositories';
|
||||
import { AuthType } from './auth.constant';
|
||||
import { AuthUserDto, SignUpDto } from './auth.dto';
|
||||
import { AuthDto, SignUpDto } from './auth.dto';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
// const token = Buffer.from('my-api-key', 'utf8').toString('base64');
|
||||
|
@ -145,7 +145,7 @@ describe('AuthService', () => {
|
|||
|
||||
describe('changePassword', () => {
|
||||
it('should change the password', async () => {
|
||||
const authUser = { email: 'test@imimch.com' } as UserEntity;
|
||||
const auth = { user: { email: 'test@imimch.com' } } as AuthDto;
|
||||
const dto = { password: 'old-password', newPassword: 'new-password' };
|
||||
|
||||
userMock.getByEmail.mockResolvedValue({
|
||||
|
@ -153,23 +153,23 @@ describe('AuthService', () => {
|
|||
password: 'hash-password',
|
||||
} as UserEntity);
|
||||
|
||||
await sut.changePassword(authUser, dto);
|
||||
await sut.changePassword(auth, dto);
|
||||
|
||||
expect(userMock.getByEmail).toHaveBeenCalledWith(authUser.email, true);
|
||||
expect(userMock.getByEmail).toHaveBeenCalledWith(auth.user.email, true);
|
||||
expect(cryptoMock.compareBcrypt).toHaveBeenCalledWith('old-password', 'hash-password');
|
||||
});
|
||||
|
||||
it('should throw when auth user email is not found', async () => {
|
||||
const authUser = { email: 'test@imimch.com' } as UserEntity;
|
||||
const auth = { user: { email: 'test@imimch.com' } } as AuthDto;
|
||||
const dto = { password: 'old-password', newPassword: 'new-password' };
|
||||
|
||||
userMock.getByEmail.mockResolvedValue(null);
|
||||
|
||||
await expect(sut.changePassword(authUser, dto)).rejects.toBeInstanceOf(UnauthorizedException);
|
||||
await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(UnauthorizedException);
|
||||
});
|
||||
|
||||
it('should throw when password does not match existing password', async () => {
|
||||
const authUser = { email: 'test@imimch.com' } as UserEntity;
|
||||
const auth = { user: { email: 'test@imimch.com' } as UserEntity };
|
||||
const dto = { password: 'old-password', newPassword: 'new-password' };
|
||||
|
||||
cryptoMock.compareBcrypt.mockReturnValue(false);
|
||||
|
@ -179,11 +179,11 @@ describe('AuthService', () => {
|
|||
password: 'hash-password',
|
||||
} as UserEntity);
|
||||
|
||||
await expect(sut.changePassword(authUser, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('should throw when user does not have a password', async () => {
|
||||
const authUser = { email: 'test@imimch.com' } as UserEntity;
|
||||
const auth = { user: { email: 'test@imimch.com' } } as AuthDto;
|
||||
const dto = { password: 'old-password', newPassword: 'new-password' };
|
||||
|
||||
userMock.getByEmail.mockResolvedValue({
|
||||
|
@ -191,33 +191,33 @@ describe('AuthService', () => {
|
|||
password: '',
|
||||
} as UserEntity);
|
||||
|
||||
await expect(sut.changePassword(authUser, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logout', () => {
|
||||
it('should return the end session endpoint', async () => {
|
||||
configMock.load.mockResolvedValue(systemConfigStub.enabled);
|
||||
const authUser = { id: '123' } as AuthUserDto;
|
||||
await expect(sut.logout(authUser, AuthType.OAUTH)).resolves.toEqual({
|
||||
const auth = { user: { id: '123' } } as AuthDto;
|
||||
await expect(sut.logout(auth, AuthType.OAUTH)).resolves.toEqual({
|
||||
successful: true,
|
||||
redirectUri: 'http://end-session-endpoint',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the default redirect', async () => {
|
||||
const authUser = { id: '123' } as AuthUserDto;
|
||||
const auth = { user: { id: '123' } } as AuthDto;
|
||||
|
||||
await expect(sut.logout(authUser, AuthType.PASSWORD)).resolves.toEqual({
|
||||
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
|
||||
successful: true,
|
||||
redirectUri: '/auth/login?autoLaunch=0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete the access token', async () => {
|
||||
const authUser = { id: '123', accessTokenId: 'token123' } as AuthUserDto;
|
||||
const auth = { user: { id: '123' }, userToken: { id: 'token123' } } as AuthDto;
|
||||
|
||||
await expect(sut.logout(authUser, AuthType.PASSWORD)).resolves.toEqual({
|
||||
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
|
||||
successful: true,
|
||||
redirectUri: '/auth/login?autoLaunch=0',
|
||||
});
|
||||
|
@ -226,9 +226,9 @@ describe('AuthService', () => {
|
|||
});
|
||||
|
||||
it('should return the default redirect if auth type is OAUTH but oauth is not enabled', async () => {
|
||||
const authUser = { id: '123' } as AuthUserDto;
|
||||
const auth = { user: { id: '123' } } as AuthDto;
|
||||
|
||||
await expect(sut.logout(authUser, AuthType.OAUTH)).resolves.toEqual({
|
||||
await expect(sut.logout(auth, AuthType.OAUTH)).resolves.toEqual({
|
||||
successful: true,
|
||||
redirectUri: '/auth/login?autoLaunch=0',
|
||||
});
|
||||
|
@ -268,7 +268,10 @@ describe('AuthService', () => {
|
|||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
userTokenMock.getByToken.mockResolvedValue(userTokenStub.userToken);
|
||||
const client = { request: { headers: { authorization: 'Bearer auth_token' } } };
|
||||
await expect(sut.validate((client as Socket).request.headers, {})).resolves.toEqual(userStub.user1);
|
||||
await expect(sut.validate((client as Socket).request.headers, {})).resolves.toEqual({
|
||||
user: userStub.user1,
|
||||
userToken: userTokenStub.userToken,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -296,7 +299,10 @@ describe('AuthService', () => {
|
|||
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
|
||||
userMock.get.mockResolvedValue(userStub.admin);
|
||||
const headers: IncomingHttpHeaders = { 'x-immich-share-key': sharedLinkStub.valid.key.toString('base64url') };
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink);
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual({
|
||||
user: userStub.admin,
|
||||
sharedLink: sharedLinkStub.valid,
|
||||
});
|
||||
expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key);
|
||||
});
|
||||
|
||||
|
@ -304,7 +310,10 @@ describe('AuthService', () => {
|
|||
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
|
||||
userMock.get.mockResolvedValue(userStub.admin);
|
||||
const headers: IncomingHttpHeaders = { 'x-immich-share-key': sharedLinkStub.valid.key.toString('hex') };
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual(authStub.adminSharedLink);
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual({
|
||||
user: userStub.admin,
|
||||
sharedLink: sharedLinkStub.valid,
|
||||
});
|
||||
expect(shareMock.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key);
|
||||
});
|
||||
});
|
||||
|
@ -319,14 +328,20 @@ describe('AuthService', () => {
|
|||
it('should return an auth dto', async () => {
|
||||
userTokenMock.getByToken.mockResolvedValue(userTokenStub.userToken);
|
||||
const headers: IncomingHttpHeaders = { cookie: 'immich_access_token=auth_token' };
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual(userStub.user1);
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual({
|
||||
user: userStub.user1,
|
||||
userToken: userTokenStub.userToken,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update when access time exceeds an hour', async () => {
|
||||
userTokenMock.getByToken.mockResolvedValue(userTokenStub.inactiveToken);
|
||||
userTokenMock.save.mockResolvedValue(userTokenStub.userToken);
|
||||
const headers: IncomingHttpHeaders = { cookie: 'immich_access_token=auth_token' };
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual(userStub.user1);
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual({
|
||||
user: userStub.user1,
|
||||
userToken: userTokenStub.userToken,
|
||||
});
|
||||
expect(userTokenMock.save.mock.calls[0][0]).toMatchObject({
|
||||
id: 'not_active',
|
||||
token: 'auth_token',
|
||||
|
@ -350,7 +365,7 @@ describe('AuthService', () => {
|
|||
it('should return an auth dto', async () => {
|
||||
keyMock.getKey.mockResolvedValue(keyStub.admin);
|
||||
const headers: IncomingHttpHeaders = { 'x-api-key': 'auth_token' };
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual(authStub.admin);
|
||||
await expect(sut.validate(headers, {})).resolves.toEqual({ user: userStub.admin, apiKey: keyStub.admin });
|
||||
expect(keyMock.getKey).toHaveBeenCalledWith('auth_token (hashed)');
|
||||
});
|
||||
});
|
||||
|
@ -377,7 +392,7 @@ describe('AuthService', () => {
|
|||
},
|
||||
]);
|
||||
|
||||
expect(userTokenMock.getAll).toHaveBeenCalledWith(authStub.user1.id);
|
||||
expect(userTokenMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -387,7 +402,7 @@ describe('AuthService', () => {
|
|||
|
||||
await sut.logoutDevices(authStub.user1);
|
||||
|
||||
expect(userTokenMock.getAll).toHaveBeenCalledWith(authStub.user1.id);
|
||||
expect(userTokenMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
|
||||
expect(userTokenMock.delete).toHaveBeenCalledWith('not_active');
|
||||
expect(userTokenMock.delete).not.toHaveBeenCalledWith('token-id');
|
||||
});
|
||||
|
@ -399,7 +414,7 @@ describe('AuthService', () => {
|
|||
|
||||
await sut.logoutDevice(authStub.user1, 'token-1');
|
||||
|
||||
expect(accessMock.authDevice.checkOwnerAccess).toHaveBeenCalledWith(authStub.user1.id, new Set(['token-1']));
|
||||
expect(accessMock.authDevice.checkOwnerAccess).toHaveBeenCalledWith(authStub.user1.user.id, new Set(['token-1']));
|
||||
expect(userTokenMock.delete).toHaveBeenCalledWith('token-1');
|
||||
});
|
||||
});
|
||||
|
@ -506,7 +521,7 @@ describe('AuthService', () => {
|
|||
|
||||
await sut.link(authStub.user1, { url: 'http://immich/user-settings?code=abc123' });
|
||||
|
||||
expect(userMock.update).toHaveBeenCalledWith(authStub.user1.id, { oauthId: sub });
|
||||
expect(userMock.update).toHaveBeenCalledWith(authStub.user1.user.id, { oauthId: sub });
|
||||
});
|
||||
|
||||
it('should not link an already linked oauth.sub', async () => {
|
||||
|
@ -528,7 +543,7 @@ describe('AuthService', () => {
|
|||
|
||||
await sut.unlink(authStub.user1);
|
||||
|
||||
expect(userMock.update).toHaveBeenCalledWith(authStub.user1.id, { oauthId: '' });
|
||||
expect(userMock.update).toHaveBeenCalledWith(authStub.user1.user.id, { oauthId: '' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
} from './auth.constant';
|
||||
import {
|
||||
AuthDeviceResponseDto,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
ChangePasswordDto,
|
||||
LoginCredentialDto,
|
||||
LoginResponseDto,
|
||||
|
@ -110,9 +110,9 @@ export class AuthService {
|
|||
return this.createLoginResponse(user, AuthType.PASSWORD, details);
|
||||
}
|
||||
|
||||
async logout(authUser: AuthUserDto, authType: AuthType): Promise<LogoutResponseDto> {
|
||||
if (authUser.accessTokenId) {
|
||||
await this.userTokenRepository.delete(authUser.accessTokenId);
|
||||
async logout(auth: AuthDto, authType: AuthType): Promise<LogoutResponseDto> {
|
||||
if (auth.userToken) {
|
||||
await this.userTokenRepository.delete(auth.userToken.id);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -121,9 +121,9 @@ export class AuthService {
|
|||
};
|
||||
}
|
||||
|
||||
async changePassword(authUser: AuthUserDto, dto: ChangePasswordDto) {
|
||||
async changePassword(auth: AuthDto, dto: ChangePasswordDto) {
|
||||
const { password, newPassword } = dto;
|
||||
const user = await this.userRepository.getByEmail(authUser.email, true);
|
||||
const user = await this.userRepository.getByEmail(auth.user.email, true);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ export class AuthService {
|
|||
throw new BadRequestException('Wrong password');
|
||||
}
|
||||
|
||||
return this.userCore.updateUser(authUser, authUser.id, { password: newPassword });
|
||||
return this.userCore.updateUser(auth.user, auth.user.id, { password: newPassword });
|
||||
}
|
||||
|
||||
async adminSignUp(dto: SignUpDto): Promise<UserResponseDto> {
|
||||
|
@ -154,7 +154,7 @@ export class AuthService {
|
|||
return mapUser(admin);
|
||||
}
|
||||
|
||||
async validate(headers: IncomingHttpHeaders, params: Record<string, string>): Promise<AuthUserDto> {
|
||||
async validate(headers: IncomingHttpHeaders, params: Record<string, string>): Promise<AuthDto> {
|
||||
const shareKey = (headers['x-immich-share-key'] || params.key) as string;
|
||||
const userToken = (headers['x-immich-user-token'] ||
|
||||
params.userToken ||
|
||||
|
@ -177,20 +177,20 @@ export class AuthService {
|
|||
throw new UnauthorizedException('Authentication required');
|
||||
}
|
||||
|
||||
async getDevices(authUser: AuthUserDto): Promise<AuthDeviceResponseDto[]> {
|
||||
const userTokens = await this.userTokenRepository.getAll(authUser.id);
|
||||
return userTokens.map((userToken) => mapUserToken(userToken, authUser.accessTokenId));
|
||||
async getDevices(auth: AuthDto): Promise<AuthDeviceResponseDto[]> {
|
||||
const userTokens = await this.userTokenRepository.getAll(auth.user.id);
|
||||
return userTokens.map((userToken) => mapUserToken(userToken, auth.userToken?.id));
|
||||
}
|
||||
|
||||
async logoutDevice(authUser: AuthUserDto, id: string): Promise<void> {
|
||||
await this.access.requirePermission(authUser, Permission.AUTH_DEVICE_DELETE, id);
|
||||
async logoutDevice(auth: AuthDto, id: string): Promise<void> {
|
||||
await this.access.requirePermission(auth, Permission.AUTH_DEVICE_DELETE, id);
|
||||
await this.userTokenRepository.delete(id);
|
||||
}
|
||||
|
||||
async logoutDevices(authUser: AuthUserDto): Promise<void> {
|
||||
const devices = await this.userTokenRepository.getAll(authUser.id);
|
||||
async logoutDevices(auth: AuthDto): Promise<void> {
|
||||
const devices = await this.userTokenRepository.getAll(auth.user.id);
|
||||
for (const device of devices) {
|
||||
if (device.id === authUser.accessTokenId) {
|
||||
if (device.id === auth.userToken?.id) {
|
||||
continue;
|
||||
}
|
||||
await this.userTokenRepository.delete(device.id);
|
||||
|
@ -284,19 +284,19 @@ export class AuthService {
|
|||
return this.createLoginResponse(user, AuthType.OAUTH, loginDetails);
|
||||
}
|
||||
|
||||
async link(user: AuthUserDto, dto: OAuthCallbackDto): Promise<UserResponseDto> {
|
||||
async link(auth: AuthDto, dto: OAuthCallbackDto): Promise<UserResponseDto> {
|
||||
const config = await this.configCore.getConfig();
|
||||
const { sub: oauthId } = await this.getOAuthProfile(config, dto.url);
|
||||
const duplicate = await this.userRepository.getByOAuthId(oauthId);
|
||||
if (duplicate && duplicate.id !== user.id) {
|
||||
if (duplicate && duplicate.id !== auth.user.id) {
|
||||
this.logger.warn(`OAuth link account failed: sub is already linked to another user (${duplicate.email}).`);
|
||||
throw new BadRequestException('This OAuth account has already been linked to another user.');
|
||||
}
|
||||
return mapUser(await this.userRepository.update(user.id, { oauthId }));
|
||||
return mapUser(await this.userRepository.update(auth.user.id, { oauthId }));
|
||||
}
|
||||
|
||||
async unlink(user: AuthUserDto): Promise<UserResponseDto> {
|
||||
return mapUser(await this.userRepository.update(user.id, { oauthId: '' }));
|
||||
async unlink(auth: AuthDto): Promise<UserResponseDto> {
|
||||
return mapUser(await this.userRepository.update(auth.user.id, { oauthId: '' }));
|
||||
}
|
||||
|
||||
private async getLogoutEndpoint(authType: AuthType): Promise<string> {
|
||||
|
@ -371,45 +371,27 @@ export class AuthService {
|
|||
return cookies[IMMICH_ACCESS_COOKIE] || null;
|
||||
}
|
||||
|
||||
private async validateSharedLink(key: string | string[]): Promise<AuthUserDto> {
|
||||
private async validateSharedLink(key: string | string[]): Promise<AuthDto> {
|
||||
key = Array.isArray(key) ? key[0] : key;
|
||||
|
||||
const bytes = Buffer.from(key, key.length === 100 ? 'hex' : 'base64url');
|
||||
const link = await this.sharedLinkRepository.getByKey(bytes);
|
||||
if (link) {
|
||||
if (!link.expiresAt || new Date(link.expiresAt) > new Date()) {
|
||||
const user = link.user;
|
||||
const sharedLink = await this.sharedLinkRepository.getByKey(bytes);
|
||||
if (sharedLink) {
|
||||
if (!sharedLink.expiresAt || new Date(sharedLink.expiresAt) > new Date()) {
|
||||
const user = sharedLink.user;
|
||||
if (user) {
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin,
|
||||
isPublicUser: true,
|
||||
sharedLinkId: link.id,
|
||||
isAllowUpload: link.allowUpload,
|
||||
isAllowDownload: link.allowDownload,
|
||||
isShowMetadata: link.showExif,
|
||||
};
|
||||
return { user, sharedLink };
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new UnauthorizedException('Invalid share key');
|
||||
}
|
||||
|
||||
private async validateApiKey(key: string): Promise<AuthUserDto> {
|
||||
private async validateApiKey(key: string): Promise<AuthDto> {
|
||||
const hashedKey = this.cryptoRepository.hashSha256(key);
|
||||
const keyEntity = await this.keyRepository.getKey(hashedKey);
|
||||
if (keyEntity?.user) {
|
||||
const user = keyEntity.user;
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
externalPath: user.externalPath,
|
||||
};
|
||||
const apiKey = await this.keyRepository.getKey(hashedKey);
|
||||
if (apiKey?.user) {
|
||||
return { user: apiKey.user, apiKey };
|
||||
}
|
||||
|
||||
throw new UnauthorizedException('Invalid API key');
|
||||
|
@ -422,26 +404,19 @@ export class AuthService {
|
|||
return this.cryptoRepository.compareBcrypt(inputPassword, user.password);
|
||||
}
|
||||
|
||||
private async validateUserToken(tokenValue: string): Promise<AuthUserDto> {
|
||||
private async validateUserToken(tokenValue: string): Promise<AuthDto> {
|
||||
const hashedToken = this.cryptoRepository.hashSha256(tokenValue);
|
||||
let token = await this.userTokenRepository.getByToken(hashedToken);
|
||||
let userToken = await this.userTokenRepository.getByToken(hashedToken);
|
||||
|
||||
if (token?.user) {
|
||||
if (userToken?.user) {
|
||||
const now = DateTime.now();
|
||||
const updatedAt = DateTime.fromJSDate(token.updatedAt);
|
||||
const updatedAt = DateTime.fromJSDate(userToken.updatedAt);
|
||||
const diff = now.diff(updatedAt, ['hours']);
|
||||
if (diff.hours > 1) {
|
||||
token = await this.userTokenRepository.save({ ...token, updatedAt: new Date() });
|
||||
userToken = await this.userTokenRepository.save({ ...userToken, updatedAt: new Date() });
|
||||
}
|
||||
|
||||
return {
|
||||
...token.user,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
isAllowDownload: true,
|
||||
isShowMetadata: true,
|
||||
accessTokenId: token.id,
|
||||
};
|
||||
return { user: userToken.user, userToken };
|
||||
}
|
||||
|
||||
throw new UnauthorizedException('Invalid user token');
|
||||
|
|
|
@ -632,7 +632,7 @@ describe(LibraryService.name, () => {
|
|||
|
||||
await expect(sut.getCount(authStub.admin)).resolves.toBe(17);
|
||||
|
||||
expect(libraryMock.getCountForUser).toHaveBeenCalledWith(authStub.admin.id);
|
||||
expect(libraryMock.getCountForUser).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -673,7 +673,7 @@ describe(LibraryService.name, () => {
|
|||
}),
|
||||
]);
|
||||
|
||||
expect(libraryMock.getAllByUserId).toHaveBeenCalledWith(authStub.admin.id);
|
||||
expect(libraryMock.getAllByUserId).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -963,10 +963,10 @@ describe(LibraryService.name, () => {
|
|||
describe('update', () => {
|
||||
it('can update library ', async () => {
|
||||
libraryMock.update.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(sut.update(authStub.admin, authStub.admin.id, {})).resolves.toBeTruthy();
|
||||
await expect(sut.update(authStub.admin, authStub.admin.user.id, {})).resolves.toBeTruthy();
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: authStub.admin.id,
|
||||
id: authStub.admin.user.id,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Stats } from 'node:fs';
|
|||
import path from 'node:path';
|
||||
import { basename, parse } from 'path';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { mimeTypes } from '../domain.constant';
|
||||
import { usePagination, validateCronExpression } from '../domain.util';
|
||||
import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryRefreshJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
|
||||
|
@ -70,22 +70,22 @@ export class LibraryService {
|
|||
});
|
||||
}
|
||||
|
||||
async getStatistics(authUser: AuthUserDto, id: string): Promise<LibraryStatsResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.LIBRARY_READ, id);
|
||||
async getStatistics(auth: AuthDto, id: string): Promise<LibraryStatsResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.LIBRARY_READ, id);
|
||||
return this.repository.getStatistics(id);
|
||||
}
|
||||
|
||||
async getCount(authUser: AuthUserDto): Promise<number> {
|
||||
return this.repository.getCountForUser(authUser.id);
|
||||
async getCount(auth: AuthDto): Promise<number> {
|
||||
return this.repository.getCountForUser(auth.user.id);
|
||||
}
|
||||
|
||||
async getAllForUser(authUser: AuthUserDto): Promise<LibraryResponseDto[]> {
|
||||
const libraries = await this.repository.getAllByUserId(authUser.id);
|
||||
async getAllForUser(auth: AuthDto): Promise<LibraryResponseDto[]> {
|
||||
const libraries = await this.repository.getAllByUserId(auth.user.id);
|
||||
return libraries.map((library) => mapLibrary(library));
|
||||
}
|
||||
|
||||
async get(authUser: AuthUserDto, id: string): Promise<LibraryResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.LIBRARY_READ, id);
|
||||
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);
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ export class LibraryService {
|
|||
return true;
|
||||
}
|
||||
|
||||
async create(authUser: AuthUserDto, dto: CreateLibraryDto): Promise<LibraryResponseDto> {
|
||||
async create(auth: AuthDto, dto: CreateLibraryDto): Promise<LibraryResponseDto> {
|
||||
switch (dto.type) {
|
||||
case LibraryType.EXTERNAL:
|
||||
if (!dto.name) {
|
||||
|
@ -120,7 +120,7 @@ export class LibraryService {
|
|||
}
|
||||
|
||||
const library = await this.repository.create({
|
||||
ownerId: authUser.id,
|
||||
ownerId: auth.user.id,
|
||||
name: dto.name,
|
||||
type: dto.type,
|
||||
importPaths: dto.importPaths ?? [],
|
||||
|
@ -131,17 +131,17 @@ export class LibraryService {
|
|||
return mapLibrary(library);
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: string, dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.LIBRARY_UPDATE, id);
|
||||
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 });
|
||||
return mapLibrary(library);
|
||||
}
|
||||
|
||||
async delete(authUser: AuthUserDto, id: string) {
|
||||
await this.access.requirePermission(authUser, Permission.LIBRARY_DELETE, id);
|
||||
async delete(auth: AuthDto, id: string) {
|
||||
await this.access.requirePermission(auth, Permission.LIBRARY_DELETE, id);
|
||||
|
||||
const library = await this.findOrFail(id);
|
||||
const uploadCount = await this.repository.getUploadLibraryCount(authUser.id);
|
||||
const uploadCount = await this.repository.getUploadLibraryCount(auth.user.id);
|
||||
if (library.type === LibraryType.UPLOAD && uploadCount <= 1) {
|
||||
throw new BadRequestException('Cannot delete the last upload library');
|
||||
}
|
||||
|
@ -294,8 +294,8 @@ export class LibraryService {
|
|||
return true;
|
||||
}
|
||||
|
||||
async queueScan(authUser: AuthUserDto, id: string, dto: ScanLibraryDto) {
|
||||
await this.access.requirePermission(authUser, Permission.LIBRARY_UPDATE, id);
|
||||
async queueScan(auth: AuthDto, id: string, dto: ScanLibraryDto) {
|
||||
await this.access.requirePermission(auth, Permission.LIBRARY_UPDATE, id);
|
||||
|
||||
const library = await this.repository.get(id);
|
||||
if (!library || library.type !== LibraryType.EXTERNAL) {
|
||||
|
@ -312,9 +312,9 @@ export class LibraryService {
|
|||
});
|
||||
}
|
||||
|
||||
async queueRemoveOffline(authUser: AuthUserDto, id: string) {
|
||||
async queueRemoveOffline(auth: AuthDto, id: string) {
|
||||
this.logger.verbose(`Removing offline files from library: ${id}`);
|
||||
await this.access.requirePermission(authUser, Permission.LIBRARY_UPDATE, id);
|
||||
await this.access.requirePermission(auth, Permission.LIBRARY_UPDATE, id);
|
||||
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.LIBRARY_REMOVE_OFFLINE,
|
||||
|
|
|
@ -60,13 +60,13 @@ describe(PartnerService.name, () => {
|
|||
it("should return a list of partners with whom I've shared my library", async () => {
|
||||
partnerMock.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]);
|
||||
await expect(sut.getAll(authStub.user1, PartnerDirection.SharedBy)).resolves.toEqual([responseDto.admin]);
|
||||
expect(partnerMock.getAll).toHaveBeenCalledWith(authStub.user1.id);
|
||||
expect(partnerMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
|
||||
});
|
||||
|
||||
it('should return a list of partners who have shared their libraries with me', async () => {
|
||||
partnerMock.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]);
|
||||
await expect(sut.getAll(authStub.user1, PartnerDirection.SharedWith)).resolves.toEqual([responseDto.admin]);
|
||||
expect(partnerMock.getAll).toHaveBeenCalledWith(authStub.user1.id);
|
||||
expect(partnerMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -75,18 +75,18 @@ describe(PartnerService.name, () => {
|
|||
partnerMock.get.mockResolvedValue(null);
|
||||
partnerMock.create.mockResolvedValue(partnerStub.adminToUser1);
|
||||
|
||||
await expect(sut.create(authStub.admin, authStub.user1.id)).resolves.toEqual(responseDto.user1);
|
||||
await expect(sut.create(authStub.admin, authStub.user1.user.id)).resolves.toEqual(responseDto.user1);
|
||||
|
||||
expect(partnerMock.create).toHaveBeenCalledWith({
|
||||
sharedById: authStub.admin.id,
|
||||
sharedWithId: authStub.user1.id,
|
||||
sharedById: authStub.admin.user.id,
|
||||
sharedWithId: authStub.user1.user.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when the partner already exists', async () => {
|
||||
partnerMock.get.mockResolvedValue(partnerStub.adminToUser1);
|
||||
|
||||
await expect(sut.create(authStub.admin, authStub.user1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.create(authStub.admin, authStub.user1.user.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(partnerMock.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -96,7 +96,7 @@ describe(PartnerService.name, () => {
|
|||
it('should remove a partner', async () => {
|
||||
partnerMock.get.mockResolvedValue(partnerStub.adminToUser1);
|
||||
|
||||
await sut.remove(authStub.admin, authStub.user1.id);
|
||||
await sut.remove(authStub.admin, authStub.user1.user.id);
|
||||
|
||||
expect(partnerMock.remove).toHaveBeenCalledWith(partnerStub.adminToUser1);
|
||||
});
|
||||
|
@ -104,7 +104,7 @@ describe(PartnerService.name, () => {
|
|||
it('should throw an error when the partner does not exist', async () => {
|
||||
partnerMock.get.mockResolvedValue(null);
|
||||
|
||||
await expect(sut.remove(authStub.admin, authStub.user1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.remove(authStub.admin, authStub.user1.user.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(partnerMock.remove).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { PartnerEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { IAccessRepository, IPartnerRepository, PartnerDirection, PartnerIds } from '../repositories';
|
||||
import { mapUser } from '../user';
|
||||
import { PartnerResponseDto, UpdatePartnerDto } from './partner.dto';
|
||||
|
@ -16,8 +16,8 @@ export class PartnerService {
|
|||
this.access = AccessCore.create(accessRepository);
|
||||
}
|
||||
|
||||
async create(authUser: AuthUserDto, sharedWithId: string): Promise<PartnerResponseDto> {
|
||||
const partnerId: PartnerIds = { sharedById: authUser.id, sharedWithId };
|
||||
async create(auth: AuthDto, sharedWithId: string): Promise<PartnerResponseDto> {
|
||||
const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId };
|
||||
const exists = await this.repository.get(partnerId);
|
||||
if (exists) {
|
||||
throw new BadRequestException(`Partner already exists`);
|
||||
|
@ -27,8 +27,8 @@ export class PartnerService {
|
|||
return this.map(partner, PartnerDirection.SharedBy);
|
||||
}
|
||||
|
||||
async remove(authUser: AuthUserDto, sharedWithId: string): Promise<void> {
|
||||
const partnerId: PartnerIds = { sharedById: authUser.id, sharedWithId };
|
||||
async remove(auth: AuthDto, sharedWithId: string): Promise<void> {
|
||||
const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId };
|
||||
const partner = await this.repository.get(partnerId);
|
||||
if (!partner) {
|
||||
throw new BadRequestException('Partner not found');
|
||||
|
@ -37,18 +37,18 @@ export class PartnerService {
|
|||
await this.repository.remove(partner);
|
||||
}
|
||||
|
||||
async getAll(authUser: AuthUserDto, direction: PartnerDirection): Promise<PartnerResponseDto[]> {
|
||||
const partners = await this.repository.getAll(authUser.id);
|
||||
async getAll(auth: AuthDto, direction: PartnerDirection): Promise<PartnerResponseDto[]> {
|
||||
const partners = await this.repository.getAll(auth.user.id);
|
||||
const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId';
|
||||
return partners
|
||||
.filter((partner) => partner.sharedBy && partner.sharedWith) // Filter out soft deleted users
|
||||
.filter((partner) => partner[key] === authUser.id)
|
||||
.filter((partner) => partner[key] === auth.user.id)
|
||||
.map((partner) => this.map(partner, direction));
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, sharedById: string, dto: UpdatePartnerDto): Promise<PartnerResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.PARTNER_UPDATE, sharedById);
|
||||
const partnerId: PartnerIds = { sharedById, sharedWithId: authUser.id };
|
||||
async update(auth: AuthDto, sharedById: string, dto: UpdatePartnerDto): Promise<PartnerResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.PARTNER_UPDATE, sharedById);
|
||||
const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id };
|
||||
|
||||
const entity = await this.repository.update({ ...partnerId, inTimeline: dto.inTimeline });
|
||||
return this.map(entity, PartnerDirection.SharedWith);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { AssetFaceEntity, PersonEntity } from '@app/infra/entities';
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { IsArray, IsBoolean, IsDate, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { Optional, ValidateUUID, toBoolean } from '../domain.util';
|
||||
|
||||
export class PersonUpdateDto {
|
||||
|
@ -156,9 +156,9 @@ export function mapFacesWithoutPerson(face: AssetFaceEntity): AssetFaceWithoutPe
|
|||
};
|
||||
}
|
||||
|
||||
export function mapFaces(face: AssetFaceEntity, authUser: AuthUserDto): AssetFaceResponseDto {
|
||||
export function mapFaces(face: AssetFaceEntity, auth: AuthDto): AssetFaceResponseDto {
|
||||
return {
|
||||
...mapFacesWithoutPerson(face),
|
||||
person: face.person?.ownerId === authUser.id ? mapPerson(face.person) : null,
|
||||
person: face.person?.ownerId === auth.user.id ? mapPerson(face.person) : null,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ describe(PersonService.name, () => {
|
|||
visible: 1,
|
||||
people: [responseDto],
|
||||
});
|
||||
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.id, {
|
||||
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.user.id, {
|
||||
minimumFaceCount: 1,
|
||||
withHidden: false,
|
||||
});
|
||||
|
@ -125,7 +125,7 @@ describe(PersonService.name, () => {
|
|||
visible: 1,
|
||||
people: [responseDto],
|
||||
});
|
||||
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.id, {
|
||||
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.user.id, {
|
||||
minimumFaceCount: 1,
|
||||
withHidden: false,
|
||||
});
|
||||
|
@ -146,7 +146,7 @@ describe(PersonService.name, () => {
|
|||
},
|
||||
],
|
||||
});
|
||||
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.id, {
|
||||
expect(personMock.getAllForUser).toHaveBeenCalledWith(authStub.admin.user.id, {
|
||||
minimumFaceCount: 1,
|
||||
withHidden: true,
|
||||
});
|
||||
|
@ -157,14 +157,14 @@ describe(PersonService.name, () => {
|
|||
it('should require person.read permission', async () => {
|
||||
personMock.getById.mockResolvedValue(personStub.withName);
|
||||
await expect(sut.getById(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should throw a bad request when person is not found', async () => {
|
||||
personMock.getById.mockResolvedValue(null);
|
||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||
await expect(sut.getById(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should get a person by id', async () => {
|
||||
|
@ -172,7 +172,7 @@ describe(PersonService.name, () => {
|
|||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||
await expect(sut.getById(authStub.admin, 'person-1')).resolves.toEqual(responseDto);
|
||||
expect(personMock.getById).toHaveBeenCalledWith('person-1');
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -181,7 +181,7 @@ describe(PersonService.name, () => {
|
|||
personMock.getById.mockResolvedValue(personStub.noName);
|
||||
await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(storageMock.createReadStream).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should throw an error when personId is invalid', async () => {
|
||||
|
@ -189,7 +189,7 @@ describe(PersonService.name, () => {
|
|||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||
await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(NotFoundException);
|
||||
expect(storageMock.createReadStream).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should throw an error when person has no thumbnail', async () => {
|
||||
|
@ -197,7 +197,7 @@ describe(PersonService.name, () => {
|
|||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||
await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(NotFoundException);
|
||||
expect(storageMock.createReadStream).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should serve the thumbnail', async () => {
|
||||
|
@ -205,7 +205,7 @@ describe(PersonService.name, () => {
|
|||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||
await sut.getThumbnail(authStub.admin, 'person-1');
|
||||
expect(storageMock.createReadStream).toHaveBeenCalledWith('/path/to/thumbnail.jpg', 'image/jpeg');
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -214,7 +214,7 @@ describe(PersonService.name, () => {
|
|||
personMock.getAssets.mockResolvedValue([assetStub.image, assetStub.video]);
|
||||
await expect(sut.getAssets(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(personMock.getAssets).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it("should return a person's assets", async () => {
|
||||
|
@ -222,7 +222,7 @@ describe(PersonService.name, () => {
|
|||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||
await sut.getAssets(authStub.admin, 'person-1');
|
||||
expect(personMock.getAssets).toHaveBeenCalledWith('person-1');
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -233,7 +233,7 @@ describe(PersonService.name, () => {
|
|||
BadRequestException,
|
||||
);
|
||||
expect(personMock.update).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should throw an error when personId is invalid', async () => {
|
||||
|
@ -243,7 +243,7 @@ describe(PersonService.name, () => {
|
|||
BadRequestException,
|
||||
);
|
||||
expect(personMock.update).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it("should update a person's name", async () => {
|
||||
|
@ -256,7 +256,7 @@ describe(PersonService.name, () => {
|
|||
|
||||
expect(personMock.getById).toHaveBeenCalledWith('person-1');
|
||||
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', name: 'Person 1' });
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it("should update a person's date of birth", async () => {
|
||||
|
@ -276,7 +276,7 @@ describe(PersonService.name, () => {
|
|||
expect(personMock.getById).toHaveBeenCalledWith('person-1');
|
||||
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: new Date('1976-06-30') });
|
||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should update a person visibility', async () => {
|
||||
|
@ -289,7 +289,7 @@ describe(PersonService.name, () => {
|
|||
|
||||
expect(personMock.getById).toHaveBeenCalledWith('person-1');
|
||||
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', isHidden: false });
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it("should update a person's thumbnailPath", async () => {
|
||||
|
@ -312,7 +312,7 @@ describe(PersonService.name, () => {
|
|||
},
|
||||
]);
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: 'person-1' } });
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should throw an error when the face feature assetId is invalid', async () => {
|
||||
|
@ -323,7 +323,7 @@ describe(PersonService.name, () => {
|
|||
BadRequestException,
|
||||
);
|
||||
expect(personMock.update).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -336,7 +336,7 @@ describe(PersonService.name, () => {
|
|||
sut.updatePeople(authStub.admin, { people: [{ id: 'person-1', name: 'Person 1' }] }),
|
||||
).resolves.toEqual([{ error: BulkIdErrorReason.UNKNOWN, id: 'person-1', success: false }]);
|
||||
expect(personMock.update).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -761,7 +761,7 @@ describe(PersonService.name, () => {
|
|||
expect(personMock.reassignFaces).not.toHaveBeenCalled();
|
||||
|
||||
expect(personMock.delete).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should merge two people', async () => {
|
||||
|
@ -784,7 +784,7 @@ describe(PersonService.name, () => {
|
|||
name: JobName.PERSON_DELETE,
|
||||
data: { id: personStub.mergePerson.id },
|
||||
});
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should throw an error when the primary person is not found', async () => {
|
||||
|
@ -796,7 +796,7 @@ describe(PersonService.name, () => {
|
|||
);
|
||||
|
||||
expect(personMock.delete).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should handle invalid merge ids', async () => {
|
||||
|
@ -811,7 +811,7 @@ describe(PersonService.name, () => {
|
|||
|
||||
expect(personMock.reassignFaces).not.toHaveBeenCalled();
|
||||
expect(personMock.delete).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should handle an error reassigning faces', async () => {
|
||||
|
@ -826,7 +826,7 @@ describe(PersonService.name, () => {
|
|||
]);
|
||||
|
||||
expect(personMock.delete).not.toHaveBeenCalled();
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -836,19 +836,19 @@ describe(PersonService.name, () => {
|
|||
personMock.getStatistics.mockResolvedValue(statistics);
|
||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||
await expect(sut.getStatistics(authStub.admin, 'person-1')).resolves.toEqual({ assets: 3 });
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
|
||||
it('should require person.read permission', async () => {
|
||||
personMock.getById.mockResolvedValue(personStub.primaryPerson);
|
||||
await expect(sut.getStatistics(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapFace', () => {
|
||||
it('should map a face', () => {
|
||||
expect(mapFaces(faceStub.face1, personStub.withName.owner)).toEqual({
|
||||
expect(mapFaces(faceStub.face1, { user: personStub.withName.owner })).toEqual({
|
||||
boundingBoxX1: 0,
|
||||
boundingBoxX2: 1,
|
||||
boundingBoxY1: 0,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { PersonPathType } from '@app/infra/entities/move.entity';
|
|||
import { BadRequestException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AssetResponseDto, BulkIdErrorReason, BulkIdResponseDto, mapAsset } from '../asset';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { mimeTypes } from '../domain.constant';
|
||||
import { usePagination } from '../domain.util';
|
||||
import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
|
||||
|
@ -65,9 +65,9 @@ export class PersonService {
|
|||
this.storageCore = StorageCore.create(assetRepository, moveRepository, repository, storageRepository);
|
||||
}
|
||||
|
||||
async getAll(authUser: AuthUserDto, dto: PersonSearchDto): Promise<PeopleResponseDto> {
|
||||
async getAll(auth: AuthDto, dto: PersonSearchDto): Promise<PeopleResponseDto> {
|
||||
const { machineLearning } = await this.configCore.getConfig();
|
||||
const people = await this.repository.getAllForUser(authUser.id, {
|
||||
const people = await this.repository.getAllForUser(auth.user.id, {
|
||||
minimumFaceCount: machineLearning.facialRecognition.minFaces,
|
||||
withHidden: dto.withHidden || false,
|
||||
});
|
||||
|
@ -83,12 +83,12 @@ export class PersonService {
|
|||
};
|
||||
}
|
||||
|
||||
createPerson(authUser: AuthUserDto): Promise<PersonResponseDto> {
|
||||
return this.repository.create({ ownerId: authUser.id });
|
||||
createPerson(auth: AuthDto): Promise<PersonResponseDto> {
|
||||
return this.repository.create({ ownerId: auth.user.id });
|
||||
}
|
||||
|
||||
async reassignFaces(authUser: AuthUserDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, personId);
|
||||
async reassignFaces(auth: AuthDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, personId);
|
||||
const person = await this.findOrFail(personId);
|
||||
const result: PersonResponseDto[] = [];
|
||||
const changeFeaturePhoto: string[] = [];
|
||||
|
@ -96,7 +96,7 @@ export class PersonService {
|
|||
const faces = await this.repository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]);
|
||||
|
||||
for (const face of faces) {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_CREATE, face.id);
|
||||
await this.access.requirePermission(auth, Permission.PERSON_CREATE, face.id);
|
||||
if (person.faceAssetId === null) {
|
||||
changeFeaturePhoto.push(person.id);
|
||||
}
|
||||
|
@ -116,10 +116,10 @@ export class PersonService {
|
|||
return result;
|
||||
}
|
||||
|
||||
async reassignFacesById(authUser: AuthUserDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, personId);
|
||||
async reassignFacesById(auth: AuthDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, personId);
|
||||
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_CREATE, dto.id);
|
||||
await this.access.requirePermission(auth, Permission.PERSON_CREATE, dto.id);
|
||||
const face = await this.repository.getFaceById(dto.id);
|
||||
const person = await this.findOrFail(personId);
|
||||
|
||||
|
@ -134,10 +134,10 @@ export class PersonService {
|
|||
return await this.findOrFail(personId).then(mapPerson);
|
||||
}
|
||||
|
||||
async getFacesById(authUser: AuthUserDto, dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_READ, dto.id);
|
||||
async getFacesById(auth: AuthDto, dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_READ, dto.id);
|
||||
const faces = await this.repository.getFaces(dto.id);
|
||||
return faces.map((asset) => mapFaces(asset, authUser));
|
||||
return faces.map((asset) => mapFaces(asset, auth));
|
||||
}
|
||||
|
||||
async createNewFeaturePhoto(changeFeaturePhoto: string[]) {
|
||||
|
@ -163,18 +163,18 @@ export class PersonService {
|
|||
}
|
||||
}
|
||||
|
||||
async getById(authUser: AuthUserDto, id: string): Promise<PersonResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
|
||||
async getById(auth: AuthDto, id: string): Promise<PersonResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_READ, id);
|
||||
return this.findOrFail(id).then(mapPerson);
|
||||
}
|
||||
|
||||
async getStatistics(authUser: AuthUserDto, id: string): Promise<PersonStatisticsResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
|
||||
async getStatistics(auth: AuthDto, id: string): Promise<PersonStatisticsResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_READ, id);
|
||||
return this.repository.getStatistics(id);
|
||||
}
|
||||
|
||||
async getThumbnail(authUser: AuthUserDto, id: string): Promise<ImmichReadStream> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
|
||||
async getThumbnail(auth: AuthDto, id: string): Promise<ImmichReadStream> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_READ, id);
|
||||
const person = await this.repository.getById(id);
|
||||
if (!person || !person.thumbnailPath) {
|
||||
throw new NotFoundException();
|
||||
|
@ -183,14 +183,14 @@ export class PersonService {
|
|||
return this.storageRepository.createReadStream(person.thumbnailPath, mimeTypes.lookup(person.thumbnailPath));
|
||||
}
|
||||
|
||||
async getAssets(authUser: AuthUserDto, id: string): Promise<AssetResponseDto[]> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
|
||||
async getAssets(auth: AuthDto, id: string): Promise<AssetResponseDto[]> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_READ, id);
|
||||
const assets = await this.repository.getAssets(id);
|
||||
return assets.map((asset) => mapAsset(asset));
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, id);
|
||||
async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, id);
|
||||
let person = await this.findOrFail(id);
|
||||
|
||||
const { name, birthDate, isHidden, featureFaceAssetId: assetId } = dto;
|
||||
|
@ -200,7 +200,7 @@ export class PersonService {
|
|||
}
|
||||
|
||||
if (assetId) {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_READ, assetId);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_READ, assetId);
|
||||
const [face] = await this.repository.getFacesByIds([{ personId: id, assetId }]);
|
||||
if (!face) {
|
||||
throw new BadRequestException('Invalid assetId for feature face');
|
||||
|
@ -213,11 +213,11 @@ export class PersonService {
|
|||
return mapPerson(person);
|
||||
}
|
||||
|
||||
async updatePeople(authUser: AuthUserDto, dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||
async updatePeople(auth: AuthDto, dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||
const results: BulkIdResponseDto[] = [];
|
||||
for (const person of dto.people) {
|
||||
try {
|
||||
await this.update(authUser, person.id, {
|
||||
await this.update(auth, person.id, {
|
||||
isHidden: person.isHidden,
|
||||
name: person.name,
|
||||
birthDate: person.birthDate,
|
||||
|
@ -438,15 +438,15 @@ export class PersonService {
|
|||
return true;
|
||||
}
|
||||
|
||||
async mergePerson(authUser: AuthUserDto, id: string, dto: MergePersonDto): Promise<BulkIdResponseDto[]> {
|
||||
async mergePerson(auth: AuthDto, id: string, dto: MergePersonDto): Promise<BulkIdResponseDto[]> {
|
||||
const mergeIds = dto.ids;
|
||||
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, id);
|
||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, id);
|
||||
const primaryPerson = await this.findOrFail(id);
|
||||
const primaryName = primaryPerson.name || primaryPerson.id;
|
||||
|
||||
const results: BulkIdResponseDto[] = [];
|
||||
|
||||
const allowedIds = await this.access.checkAccess(authUser, Permission.PERSON_MERGE, mergeIds);
|
||||
const allowedIds = await this.access.checkAccess(auth, Permission.PERSON_MERGE, mergeIds);
|
||||
|
||||
for (const mergeId of mergeIds) {
|
||||
const hasAccess = allowedIds.has(mergeId);
|
||||
|
|
|
@ -49,11 +49,11 @@ describe(SearchService.name, () => {
|
|||
|
||||
await sut.searchPerson(authStub.user1, { name, withHidden: false });
|
||||
|
||||
expect(personMock.getByName).toHaveBeenCalledWith(authStub.user1.id, name, { withHidden: false });
|
||||
expect(personMock.getByName).toHaveBeenCalledWith(authStub.user1.user.id, name, { withHidden: false });
|
||||
|
||||
await sut.searchPerson(authStub.user1, { name, withHidden: true });
|
||||
|
||||
expect(personMock.getByName).toHaveBeenCalledWith(authStub.user1.id, name, { withHidden: true });
|
||||
expect(personMock.getByName).toHaveBeenCalledWith(authStub.user1.user.id, name, { withHidden: true });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -105,7 +105,7 @@ describe(SearchService.name, () => {
|
|||
const result = await sut.search(authStub.user1, dto);
|
||||
|
||||
expect(result).toEqual(expectedResponse);
|
||||
expect(assetMock.searchMetadata).toHaveBeenCalledWith(dto.q, authStub.user1.id, { numResults: 250 });
|
||||
expect(assetMock.searchMetadata).toHaveBeenCalledWith(dto.q, authStub.user1.user.id, { numResults: 250 });
|
||||
expect(smartInfoMock.searchCLIP).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -132,7 +132,11 @@ describe(SearchService.name, () => {
|
|||
const result = await sut.search(authStub.user1, dto);
|
||||
|
||||
expect(result).toEqual(expectedResponse);
|
||||
expect(smartInfoMock.searchCLIP).toHaveBeenCalledWith({ ownerId: authStub.user1.id, embedding, numResults: 100 });
|
||||
expect(smartInfoMock.searchCLIP).toHaveBeenCalledWith({
|
||||
ownerId: authStub.user1.user.id,
|
||||
embedding,
|
||||
numResults: 100,
|
||||
});
|
||||
expect(assetMock.searchMetadata).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AssetEntity } from '@app/infra/entities';
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { AssetResponseDto, mapAsset } from '../asset';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { PersonResponseDto } from '../person';
|
||||
import {
|
||||
IAssetRepository,
|
||||
|
@ -31,16 +31,16 @@ export class SearchService {
|
|||
this.configCore = SystemConfigCore.create(configRepository);
|
||||
}
|
||||
|
||||
async searchPerson(authUser: AuthUserDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
||||
return this.personRepository.getByName(authUser.id, dto.name, { withHidden: dto.withHidden });
|
||||
async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
||||
return this.personRepository.getByName(auth.user.id, dto.name, { withHidden: dto.withHidden });
|
||||
}
|
||||
|
||||
async getExploreData(authUser: AuthUserDto): Promise<SearchExploreItem<AssetResponseDto>[]> {
|
||||
async getExploreData(auth: AuthDto): Promise<SearchExploreItem<AssetResponseDto>[]> {
|
||||
await this.configCore.requireFeature(FeatureFlag.SEARCH);
|
||||
const options = { maxFields: 12, minAssetsPerField: 5 };
|
||||
const results = await Promise.all([
|
||||
this.assetRepository.getAssetIdByCity(authUser.id, options),
|
||||
this.assetRepository.getAssetIdByTag(authUser.id, options),
|
||||
this.assetRepository.getAssetIdByCity(auth.user.id, options),
|
||||
this.assetRepository.getAssetIdByTag(auth.user.id, options),
|
||||
]);
|
||||
const assetIds = new Set<string>(results.flatMap((field) => field.items.map((item) => item.data)));
|
||||
const assets = await this.assetRepository.getByIds(Array.from(assetIds));
|
||||
|
@ -52,7 +52,7 @@ export class SearchService {
|
|||
}));
|
||||
}
|
||||
|
||||
async search(authUser: AuthUserDto, dto: SearchDto): Promise<SearchResponseDto> {
|
||||
async search(auth: AuthDto, dto: SearchDto): Promise<SearchResponseDto> {
|
||||
const { machineLearning } = await this.configCore.getConfig();
|
||||
const query = dto.q || dto.query;
|
||||
if (!query) {
|
||||
|
@ -73,10 +73,10 @@ export class SearchService {
|
|||
{ text: query },
|
||||
machineLearning.clip,
|
||||
);
|
||||
assets = await this.smartInfoRepository.searchCLIP({ ownerId: authUser.id, embedding, numResults: 100 });
|
||||
assets = await this.smartInfoRepository.searchCLIP({ ownerId: auth.user.id, embedding, numResults: 100 });
|
||||
break;
|
||||
case SearchStrategy.TEXT:
|
||||
assets = await this.assetRepository.searchMetadata(query, authUser.id, { numResults: 250 });
|
||||
assets = await this.assetRepository.searchMetadata(query, auth.user.id, { numResults: 250 });
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ describe(SharedLinkService.name, () => {
|
|||
sharedLinkResponseStub.expired,
|
||||
sharedLinkResponseStub.valid,
|
||||
]);
|
||||
expect(shareMock.getAll).toHaveBeenCalledWith(authStub.user1.id);
|
||||
expect(shareMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -55,21 +55,21 @@ describe(SharedLinkService.name, () => {
|
|||
const authDto = authStub.adminSharedLink;
|
||||
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
await expect(sut.getMine(authDto, {})).resolves.toEqual(sharedLinkResponseStub.valid);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
|
||||
});
|
||||
|
||||
it('should not return metadata', async () => {
|
||||
const authDto = authStub.adminSharedLinkNoExif;
|
||||
shareMock.get.mockResolvedValue(sharedLinkStub.readonlyNoExif);
|
||||
await expect(sut.getMine(authDto, {})).resolves.toEqual(sharedLinkResponseStub.readonlyNoMetadata);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
|
||||
});
|
||||
|
||||
it('should throw an error for an password protected shared link', async () => {
|
||||
const authDto = authStub.adminSharedLink;
|
||||
shareMock.get.mockResolvedValue(sharedLinkStub.passwordRequired);
|
||||
await expect(sut.getMine(authDto, {})).rejects.toBeInstanceOf(UnauthorizedException);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -77,14 +77,14 @@ describe(SharedLinkService.name, () => {
|
|||
it('should throw an error for an invalid shared link', async () => {
|
||||
shareMock.get.mockResolvedValue(null);
|
||||
await expect(sut.get(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, 'missing-id');
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
|
||||
expect(shareMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should get a shared link by id', async () => {
|
||||
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
await expect(sut.get(authStub.user1, sharedLinkStub.valid.id)).resolves.toEqual(sharedLinkResponseStub.valid);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -120,12 +120,12 @@ describe(SharedLinkService.name, () => {
|
|||
await sut.create(authStub.admin, { type: SharedLinkType.ALBUM, albumId: albumStub.oneAsset.id });
|
||||
|
||||
expect(accessMock.album.checkOwnerAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.id,
|
||||
authStub.admin.user.id,
|
||||
new Set([albumStub.oneAsset.id]),
|
||||
);
|
||||
expect(shareMock.create).toHaveBeenCalledWith({
|
||||
type: SharedLinkType.ALBUM,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
albumId: albumStub.oneAsset.id,
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
|
@ -149,10 +149,13 @@ describe(SharedLinkService.name, () => {
|
|||
allowUpload: true,
|
||||
});
|
||||
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set([assetStub.image.id]));
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.user.id,
|
||||
new Set([assetStub.image.id]),
|
||||
);
|
||||
expect(shareMock.create).toHaveBeenCalledWith({
|
||||
type: SharedLinkType.INDIVIDUAL,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
albumId: null,
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
|
@ -169,7 +172,7 @@ describe(SharedLinkService.name, () => {
|
|||
it('should throw an error for an invalid shared link', async () => {
|
||||
shareMock.get.mockResolvedValue(null);
|
||||
await expect(sut.update(authStub.user1, 'missing-id', {})).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, 'missing-id');
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
|
||||
expect(shareMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -177,10 +180,10 @@ describe(SharedLinkService.name, () => {
|
|||
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
shareMock.update.mockResolvedValue(sharedLinkStub.valid);
|
||||
await sut.update(authStub.user1, sharedLinkStub.valid.id, { allowDownload: false });
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
|
||||
expect(shareMock.update).toHaveBeenCalledWith({
|
||||
id: sharedLinkStub.valid.id,
|
||||
userId: authStub.user1.id,
|
||||
userId: authStub.user1.user.id,
|
||||
allowDownload: false,
|
||||
});
|
||||
});
|
||||
|
@ -190,14 +193,14 @@ describe(SharedLinkService.name, () => {
|
|||
it('should throw an error for an invalid shared link', async () => {
|
||||
shareMock.get.mockResolvedValue(null);
|
||||
await expect(sut.remove(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, 'missing-id');
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id');
|
||||
expect(shareMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove a key', async () => {
|
||||
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
await sut.remove(authStub.user1, sharedLinkStub.valid.id);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
|
||||
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
|
||||
expect(shareMock.remove).toHaveBeenCalledWith(sharedLinkStub.valid);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import { AssetEntity, SharedLinkEntity, SharedLinkType } from '@app/infra/entiti
|
|||
import { BadRequestException, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AssetIdErrorReason, AssetIdsDto, AssetIdsResponseDto } from '../asset';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { IAccessRepository, ICryptoRepository, ISharedLinkRepository } from '../repositories';
|
||||
import { SharedLinkResponseDto, mapSharedLink, mapSharedLinkWithoutMetadata } from './shared-link-response.dto';
|
||||
import { SharedLinkCreateDto, SharedLinkEditDto, SharedLinkPasswordDto } from './shared-link.dto';
|
||||
|
@ -19,42 +19,36 @@ export class SharedLinkService {
|
|||
this.access = AccessCore.create(accessRepository);
|
||||
}
|
||||
|
||||
getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.repository.getAll(authUser.id).then((links) => links.map(mapSharedLink));
|
||||
getAll(auth: AuthDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.repository.getAll(auth.user.id).then((links) => links.map(mapSharedLink));
|
||||
}
|
||||
|
||||
async getMine(authUser: AuthUserDto, dto: SharedLinkPasswordDto): Promise<SharedLinkResponseDto> {
|
||||
const { sharedLinkId: id, isPublicUser, isShowMetadata: isShowExif } = authUser;
|
||||
|
||||
if (!isPublicUser || !id) {
|
||||
async getMine(auth: AuthDto, dto: SharedLinkPasswordDto): Promise<SharedLinkResponseDto> {
|
||||
if (!auth.sharedLink) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
const sharedLink = await this.findOrFail(authUser, id);
|
||||
|
||||
let newToken;
|
||||
const sharedLink = await this.findOrFail(auth, auth.sharedLink.id);
|
||||
const response = this.map(sharedLink, { withExif: sharedLink.showExif });
|
||||
if (sharedLink.password) {
|
||||
newToken = this.validateAndRefreshToken(sharedLink, dto);
|
||||
response.token = this.validateAndRefreshToken(sharedLink, dto);
|
||||
}
|
||||
|
||||
return {
|
||||
...this.map(sharedLink, { withExif: isShowExif ?? true }),
|
||||
token: newToken,
|
||||
};
|
||||
return response;
|
||||
}
|
||||
|
||||
async get(authUser: AuthUserDto, id: string): Promise<SharedLinkResponseDto> {
|
||||
const sharedLink = await this.findOrFail(authUser, id);
|
||||
async get(auth: AuthDto, id: string): Promise<SharedLinkResponseDto> {
|
||||
const sharedLink = await this.findOrFail(auth, id);
|
||||
return this.map(sharedLink, { withExif: true });
|
||||
}
|
||||
|
||||
async create(authUser: AuthUserDto, dto: SharedLinkCreateDto): Promise<SharedLinkResponseDto> {
|
||||
async create(auth: AuthDto, dto: SharedLinkCreateDto): Promise<SharedLinkResponseDto> {
|
||||
switch (dto.type) {
|
||||
case SharedLinkType.ALBUM:
|
||||
if (!dto.albumId) {
|
||||
throw new BadRequestException('Invalid albumId');
|
||||
}
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_SHARE, dto.albumId);
|
||||
await this.access.requirePermission(auth, Permission.ALBUM_SHARE, dto.albumId);
|
||||
break;
|
||||
|
||||
case SharedLinkType.INDIVIDUAL:
|
||||
|
@ -62,14 +56,14 @@ export class SharedLinkService {
|
|||
throw new BadRequestException('Invalid assetIds');
|
||||
}
|
||||
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_SHARE, dto.assetIds);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_SHARE, dto.assetIds);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const sharedLink = await this.repository.create({
|
||||
key: this.cryptoRepository.randomBytes(50),
|
||||
userId: authUser.id,
|
||||
userId: auth.user.id,
|
||||
type: dto.type,
|
||||
albumId: dto.albumId || null,
|
||||
assets: (dto.assetIds || []).map((id) => ({ id }) as AssetEntity),
|
||||
|
@ -84,11 +78,11 @@ export class SharedLinkService {
|
|||
return this.map(sharedLink, { withExif: true });
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: string, dto: SharedLinkEditDto) {
|
||||
await this.findOrFail(authUser, id);
|
||||
async update(auth: AuthDto, id: string, dto: SharedLinkEditDto) {
|
||||
await this.findOrFail(auth, id);
|
||||
const sharedLink = await this.repository.update({
|
||||
id,
|
||||
userId: authUser.id,
|
||||
userId: auth.user.id,
|
||||
description: dto.description,
|
||||
password: dto.password,
|
||||
expiresAt: dto.changeExpiryTime && !dto.expiresAt ? null : dto.expiresAt,
|
||||
|
@ -99,21 +93,21 @@ export class SharedLinkService {
|
|||
return this.map(sharedLink, { withExif: true });
|
||||
}
|
||||
|
||||
async remove(authUser: AuthUserDto, id: string): Promise<void> {
|
||||
const sharedLink = await this.findOrFail(authUser, id);
|
||||
async remove(auth: AuthDto, id: string): Promise<void> {
|
||||
const sharedLink = await this.findOrFail(auth, id);
|
||||
await this.repository.remove(sharedLink);
|
||||
}
|
||||
|
||||
private async findOrFail(authUser: AuthUserDto, id: string) {
|
||||
const sharedLink = await this.repository.get(authUser.id, id);
|
||||
private async findOrFail(auth: AuthDto, id: string) {
|
||||
const sharedLink = await this.repository.get(auth.user.id, id);
|
||||
if (!sharedLink) {
|
||||
throw new BadRequestException('Shared link not found');
|
||||
}
|
||||
return sharedLink;
|
||||
}
|
||||
|
||||
async addAssets(authUser: AuthUserDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
|
||||
const sharedLink = await this.findOrFail(authUser, id);
|
||||
async addAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
|
||||
const sharedLink = await this.findOrFail(auth, id);
|
||||
|
||||
if (sharedLink.type !== SharedLinkType.INDIVIDUAL) {
|
||||
throw new BadRequestException('Invalid shared link type');
|
||||
|
@ -121,7 +115,7 @@ export class SharedLinkService {
|
|||
|
||||
const existingAssetIds = new Set(sharedLink.assets.map((asset) => asset.id));
|
||||
const notPresentAssetIds = dto.assetIds.filter((assetId) => !existingAssetIds.has(assetId));
|
||||
const allowedAssetIds = await this.access.checkAccess(authUser, Permission.ASSET_SHARE, notPresentAssetIds);
|
||||
const allowedAssetIds = await this.access.checkAccess(auth, Permission.ASSET_SHARE, notPresentAssetIds);
|
||||
|
||||
const results: AssetIdsResponseDto[] = [];
|
||||
for (const assetId of dto.assetIds) {
|
||||
|
@ -146,8 +140,8 @@ export class SharedLinkService {
|
|||
return results;
|
||||
}
|
||||
|
||||
async removeAssets(authUser: AuthUserDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
|
||||
const sharedLink = await this.findOrFail(authUser, id);
|
||||
async removeAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
|
||||
const sharedLink = await this.findOrFail(auth, id);
|
||||
|
||||
if (sharedLink.type !== SharedLinkType.INDIVIDUAL) {
|
||||
throw new BadRequestException('Invalid shared link type');
|
||||
|
|
|
@ -23,7 +23,7 @@ describe(TagService.name, () => {
|
|||
it('should return all tags for a user', async () => {
|
||||
tagMock.getAll.mockResolvedValue([tagStub.tag1]);
|
||||
await expect(sut.getAll(authStub.admin)).resolves.toEqual([tagResponseStub.tag1]);
|
||||
expect(tagMock.getAll).toHaveBeenCalledWith(authStub.admin.id);
|
||||
expect(tagMock.getAll).toHaveBeenCalledWith(authStub.admin.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -31,13 +31,13 @@ describe(TagService.name, () => {
|
|||
it('should throw an error for an invalid id', async () => {
|
||||
tagMock.getById.mockResolvedValue(null);
|
||||
await expect(sut.getById(authStub.admin, 'tag-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
});
|
||||
|
||||
it('should return a tag for a user', async () => {
|
||||
tagMock.getById.mockResolvedValue(tagStub.tag1);
|
||||
await expect(sut.getById(authStub.admin, 'tag-1')).resolves.toEqual(tagResponseStub.tag1);
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -47,7 +47,7 @@ describe(TagService.name, () => {
|
|||
await expect(sut.create(authStub.admin, { name: 'tag-1', type: TagType.CUSTOM })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
expect(tagMock.hasName).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.hasName).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -57,7 +57,7 @@ describe(TagService.name, () => {
|
|||
tagResponseStub.tag1,
|
||||
);
|
||||
expect(tagMock.create).toHaveBeenCalledWith({
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
name: 'tag-1',
|
||||
type: TagType.CUSTOM,
|
||||
});
|
||||
|
@ -68,7 +68,7 @@ describe(TagService.name, () => {
|
|||
it('should throw an error for an invalid id', async () => {
|
||||
tagMock.getById.mockResolvedValue(null);
|
||||
await expect(sut.update(authStub.admin, 'tag-1', { name: 'tag-2' })).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.remove).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -76,7 +76,7 @@ describe(TagService.name, () => {
|
|||
tagMock.getById.mockResolvedValue(tagStub.tag1);
|
||||
tagMock.update.mockResolvedValue(tagStub.tag1);
|
||||
await expect(sut.update(authStub.admin, 'tag-1', { name: 'tag-2' })).resolves.toEqual(tagResponseStub.tag1);
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.update).toHaveBeenCalledWith({ id: 'tag-1', name: 'tag-2' });
|
||||
});
|
||||
});
|
||||
|
@ -85,14 +85,14 @@ describe(TagService.name, () => {
|
|||
it('should throw an error for an invalid id', async () => {
|
||||
tagMock.getById.mockResolvedValue(null);
|
||||
await expect(sut.remove(authStub.admin, 'tag-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.remove).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove a tag', async () => {
|
||||
tagMock.getById.mockResolvedValue(tagStub.tag1);
|
||||
await sut.remove(authStub.admin, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.remove).toHaveBeenCalledWith(tagStub.tag1);
|
||||
});
|
||||
});
|
||||
|
@ -101,7 +101,7 @@ describe(TagService.name, () => {
|
|||
it('should throw an error for an invalid id', async () => {
|
||||
tagMock.getById.mockResolvedValue(null);
|
||||
await expect(sut.remove(authStub.admin, 'tag-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.remove).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -109,8 +109,8 @@ describe(TagService.name, () => {
|
|||
tagMock.getById.mockResolvedValue(tagStub.tag1);
|
||||
tagMock.getAssets.mockResolvedValue([assetStub.image]);
|
||||
await sut.getAssets(authStub.admin, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.getAssets).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -120,15 +120,15 @@ describe(TagService.name, () => {
|
|||
await expect(sut.addAssets(authStub.admin, 'tag-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.addAssets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reject duplicate asset ids and accept new ones', async () => {
|
||||
tagMock.getById.mockResolvedValue(tagStub.tag1);
|
||||
|
||||
when(tagMock.hasAsset).calledWith(authStub.admin.id, 'tag-1', 'asset-1').mockResolvedValue(true);
|
||||
when(tagMock.hasAsset).calledWith(authStub.admin.id, 'tag-1', 'asset-2').mockResolvedValue(false);
|
||||
when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-1').mockResolvedValue(true);
|
||||
when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-2').mockResolvedValue(false);
|
||||
|
||||
await expect(
|
||||
sut.addAssets(authStub.admin, 'tag-1', {
|
||||
|
@ -139,9 +139,9 @@ describe(TagService.name, () => {
|
|||
{ assetId: 'asset-2', success: true },
|
||||
]);
|
||||
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.hasAsset).toHaveBeenCalledTimes(2);
|
||||
expect(tagMock.addAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1', ['asset-2']);
|
||||
expect(tagMock.addAssets).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1', ['asset-2']);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -151,15 +151,15 @@ describe(TagService.name, () => {
|
|||
await expect(sut.removeAssets(authStub.admin, 'tag-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.removeAssets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should accept accept ids that are tagged and reject the rest', async () => {
|
||||
tagMock.getById.mockResolvedValue(tagStub.tag1);
|
||||
|
||||
when(tagMock.hasAsset).calledWith(authStub.admin.id, 'tag-1', 'asset-1').mockResolvedValue(true);
|
||||
when(tagMock.hasAsset).calledWith(authStub.admin.id, 'tag-1', 'asset-2').mockResolvedValue(false);
|
||||
when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-1').mockResolvedValue(true);
|
||||
when(tagMock.hasAsset).calledWith(authStub.admin.user.id, 'tag-1', 'asset-2').mockResolvedValue(false);
|
||||
|
||||
await expect(
|
||||
sut.removeAssets(authStub.admin, 'tag-1', {
|
||||
|
@ -170,9 +170,9 @@ describe(TagService.name, () => {
|
|||
{ assetId: 'asset-2', success: false, error: AssetIdErrorReason.NOT_FOUND },
|
||||
]);
|
||||
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1');
|
||||
expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1');
|
||||
expect(tagMock.hasAsset).toHaveBeenCalledTimes(2);
|
||||
expect(tagMock.removeAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1', ['asset-1']);
|
||||
expect(tagMock.removeAssets).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1', ['asset-1']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { AssetIdErrorReason, AssetIdsDto, AssetIdsResponseDto, AssetResponseDto, mapAsset } from '../asset';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { ITagRepository } from '../repositories';
|
||||
import { TagResponseDto, mapTag } from './tag-response.dto';
|
||||
import { CreateTagDto, UpdateTagDto } from './tag.dto';
|
||||
|
@ -9,23 +9,23 @@ import { CreateTagDto, UpdateTagDto } from './tag.dto';
|
|||
export class TagService {
|
||||
constructor(@Inject(ITagRepository) private repository: ITagRepository) {}
|
||||
|
||||
getAll(authUser: AuthUserDto) {
|
||||
return this.repository.getAll(authUser.id).then((tags) => tags.map(mapTag));
|
||||
getAll(auth: AuthDto) {
|
||||
return this.repository.getAll(auth.user.id).then((tags) => tags.map(mapTag));
|
||||
}
|
||||
|
||||
async getById(authUser: AuthUserDto, id: string): Promise<TagResponseDto> {
|
||||
const tag = await this.findOrFail(authUser, id);
|
||||
async getById(auth: AuthDto, id: string): Promise<TagResponseDto> {
|
||||
const tag = await this.findOrFail(auth, id);
|
||||
return mapTag(tag);
|
||||
}
|
||||
|
||||
async create(authUser: AuthUserDto, dto: CreateTagDto) {
|
||||
const duplicate = await this.repository.hasName(authUser.id, dto.name);
|
||||
async create(auth: AuthDto, dto: CreateTagDto) {
|
||||
const duplicate = await this.repository.hasName(auth.user.id, dto.name);
|
||||
if (duplicate) {
|
||||
throw new BadRequestException(`A tag with that name already exists`);
|
||||
}
|
||||
|
||||
const tag = await this.repository.create({
|
||||
userId: authUser.id,
|
||||
userId: auth.user.id,
|
||||
name: dto.name,
|
||||
type: dto.type,
|
||||
});
|
||||
|
@ -33,29 +33,29 @@ export class TagService {
|
|||
return mapTag(tag);
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: string, dto: UpdateTagDto): Promise<TagResponseDto> {
|
||||
await this.findOrFail(authUser, id);
|
||||
async update(auth: AuthDto, id: string, dto: UpdateTagDto): Promise<TagResponseDto> {
|
||||
await this.findOrFail(auth, id);
|
||||
const tag = await this.repository.update({ id, name: dto.name });
|
||||
return mapTag(tag);
|
||||
}
|
||||
|
||||
async remove(authUser: AuthUserDto, id: string): Promise<void> {
|
||||
const tag = await this.findOrFail(authUser, id);
|
||||
async remove(auth: AuthDto, id: string): Promise<void> {
|
||||
const tag = await this.findOrFail(auth, id);
|
||||
await this.repository.remove(tag);
|
||||
}
|
||||
|
||||
async getAssets(authUser: AuthUserDto, id: string): Promise<AssetResponseDto[]> {
|
||||
await this.findOrFail(authUser, id);
|
||||
const assets = await this.repository.getAssets(authUser.id, id);
|
||||
async getAssets(auth: AuthDto, id: string): Promise<AssetResponseDto[]> {
|
||||
await this.findOrFail(auth, id);
|
||||
const assets = await this.repository.getAssets(auth.user.id, id);
|
||||
return assets.map((asset) => mapAsset(asset));
|
||||
}
|
||||
|
||||
async addAssets(authUser: AuthUserDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
|
||||
await this.findOrFail(authUser, id);
|
||||
async addAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
|
||||
await this.findOrFail(auth, id);
|
||||
|
||||
const results: AssetIdsResponseDto[] = [];
|
||||
for (const assetId of dto.assetIds) {
|
||||
const hasAsset = await this.repository.hasAsset(authUser.id, id, assetId);
|
||||
const hasAsset = await this.repository.hasAsset(auth.user.id, id, assetId);
|
||||
if (hasAsset) {
|
||||
results.push({ assetId, success: false, error: AssetIdErrorReason.DUPLICATE });
|
||||
} else {
|
||||
|
@ -64,7 +64,7 @@ export class TagService {
|
|||
}
|
||||
|
||||
await this.repository.addAssets(
|
||||
authUser.id,
|
||||
auth.user.id,
|
||||
id,
|
||||
results.filter((result) => result.success).map((result) => result.assetId),
|
||||
);
|
||||
|
@ -72,12 +72,12 @@ export class TagService {
|
|||
return results;
|
||||
}
|
||||
|
||||
async removeAssets(authUser: AuthUserDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
|
||||
await this.findOrFail(authUser, id);
|
||||
async removeAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise<AssetIdsResponseDto[]> {
|
||||
await this.findOrFail(auth, id);
|
||||
|
||||
const results: AssetIdsResponseDto[] = [];
|
||||
for (const assetId of dto.assetIds) {
|
||||
const hasAsset = await this.repository.hasAsset(authUser.id, id, assetId);
|
||||
const hasAsset = await this.repository.hasAsset(auth.user.id, id, assetId);
|
||||
if (!hasAsset) {
|
||||
results.push({ assetId, success: false, error: AssetIdErrorReason.NOT_FOUND });
|
||||
} else {
|
||||
|
@ -86,7 +86,7 @@ export class TagService {
|
|||
}
|
||||
|
||||
await this.repository.removeAssets(
|
||||
authUser.id,
|
||||
auth.user.id,
|
||||
id,
|
||||
results.filter((result) => result.success).map((result) => result.assetId),
|
||||
);
|
||||
|
@ -94,8 +94,8 @@ export class TagService {
|
|||
return results;
|
||||
}
|
||||
|
||||
private async findOrFail(authUser: AuthUserDto, id: string) {
|
||||
const tag = await this.repository.getById(authUser.id, id);
|
||||
private async findOrFail(auth: AuthDto, id: string) {
|
||||
const tag = await this.repository.getById(auth.user.id, id);
|
||||
if (!tag) {
|
||||
throw new BadRequestException('Tag not found');
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ import { LibraryType, UserEntity } from '@app/infra/entities';
|
|||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||
import path from 'path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { ICryptoRepository, ILibraryRepository, IUserRepository } from '../repositories';
|
||||
import { UserResponseDto } from './response-dto';
|
||||
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
|
@ -32,17 +32,18 @@ export class UserCore {
|
|||
instance = null;
|
||||
}
|
||||
|
||||
async updateUser(authUser: AuthUserDto, id: string, dto: Partial<UserEntity>): Promise<UserEntity> {
|
||||
if (!authUser.isAdmin && authUser.id !== id) {
|
||||
// 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) {
|
||||
throw new ForbiddenException('You are not allowed to update this user');
|
||||
}
|
||||
|
||||
if (!authUser.isAdmin) {
|
||||
if (!user.isAdmin) {
|
||||
// Users can never update the isAdmin property.
|
||||
delete dto.isAdmin;
|
||||
delete dto.storageLabel;
|
||||
delete dto.externalPath;
|
||||
} else if (dto.isAdmin && authUser.id !== id) {
|
||||
} else if (dto.isAdmin && user.id !== id) {
|
||||
// Admin cannot create another admin.
|
||||
throw new BadRequestException('The server already has an admin');
|
||||
}
|
||||
|
|
|
@ -60,10 +60,10 @@ describe(UserService.name, () => {
|
|||
|
||||
sut = new UserService(albumMock, assetMock, cryptoRepositoryMock, jobMock, libraryMock, storageMock, userMock);
|
||||
|
||||
when(userMock.get).calledWith(authStub.admin.id, {}).mockResolvedValue(userStub.admin);
|
||||
when(userMock.get).calledWith(authStub.admin.id, { withDeleted: true }).mockResolvedValue(userStub.admin);
|
||||
when(userMock.get).calledWith(authStub.user1.id, {}).mockResolvedValue(userStub.user1);
|
||||
when(userMock.get).calledWith(authStub.user1.id, { withDeleted: true }).mockResolvedValue(userStub.user1);
|
||||
when(userMock.get).calledWith(authStub.admin.user.id, {}).mockResolvedValue(userStub.admin);
|
||||
when(userMock.get).calledWith(authStub.admin.user.id, { withDeleted: true }).mockResolvedValue(userStub.admin);
|
||||
when(userMock.get).calledWith(authStub.user1.user.id, {}).mockResolvedValue(userStub.user1);
|
||||
when(userMock.get).calledWith(authStub.user1.user.id, { withDeleted: true }).mockResolvedValue(userStub.user1);
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
|
@ -71,8 +71,8 @@ describe(UserService.name, () => {
|
|||
userMock.getList.mockResolvedValue([userStub.admin]);
|
||||
await expect(sut.getAll(authStub.admin, false)).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
id: authStub.admin.id,
|
||||
email: authStub.admin.email,
|
||||
id: authStub.admin.user.id,
|
||||
email: authStub.admin.user.email,
|
||||
}),
|
||||
]);
|
||||
expect(userMock.getList).toHaveBeenCalledWith({ withDeleted: true });
|
||||
|
@ -82,14 +82,14 @@ describe(UserService.name, () => {
|
|||
describe('get', () => {
|
||||
it('should get a user by id', async () => {
|
||||
userMock.get.mockResolvedValue(userStub.admin);
|
||||
await sut.get(authStub.admin.id);
|
||||
expect(userMock.get).toHaveBeenCalledWith(authStub.admin.id, { withDeleted: false });
|
||||
await sut.get(authStub.admin.user.id);
|
||||
expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false });
|
||||
});
|
||||
|
||||
it('should throw an error if a user is not found', async () => {
|
||||
userMock.get.mockResolvedValue(null);
|
||||
await expect(sut.get(authStub.admin.id)).rejects.toBeInstanceOf(NotFoundException);
|
||||
expect(userMock.get).toHaveBeenCalledWith(authStub.admin.id, { withDeleted: false });
|
||||
await expect(sut.get(authStub.admin.user.id)).rejects.toBeInstanceOf(NotFoundException);
|
||||
expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -97,13 +97,13 @@ describe(UserService.name, () => {
|
|||
it("should get the auth user's info", async () => {
|
||||
userMock.get.mockResolvedValue(userStub.admin);
|
||||
await sut.getMe(authStub.admin);
|
||||
expect(userMock.get).toHaveBeenCalledWith(authStub.admin.id, {});
|
||||
expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, {});
|
||||
});
|
||||
|
||||
it('should throw an error if a user is not found', async () => {
|
||||
userMock.get.mockResolvedValue(null);
|
||||
await expect(sut.getMe(authStub.admin)).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(userMock.get).toHaveBeenCalledWith(authStub.admin.id, {});
|
||||
expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, {});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -119,7 +119,7 @@ describe(UserService.name, () => {
|
|||
userMock.getByStorageLabel.mockResolvedValue(null);
|
||||
userMock.update.mockResolvedValue(userStub.user1);
|
||||
|
||||
await sut.update({ ...authStub.user1, isAdmin: true }, update);
|
||||
await sut.update({ user: { ...authStub.user1.user, isAdmin: true } }, update);
|
||||
|
||||
expect(userMock.getByEmail).toHaveBeenCalledWith(update.email);
|
||||
expect(userMock.getByStorageLabel).toHaveBeenCalledWith(update.storageLabel);
|
||||
|
@ -127,13 +127,16 @@ describe(UserService.name, () => {
|
|||
|
||||
it('should not set an empty string for storage label', async () => {
|
||||
userMock.update.mockResolvedValue(userStub.user1);
|
||||
await sut.update(userStub.admin, { id: userStub.user1.id, storageLabel: '' });
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { id: userStub.user1.id, storageLabel: null });
|
||||
await sut.update(authStub.admin, { id: userStub.user1.id, storageLabel: '' });
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
|
||||
id: userStub.user1.id,
|
||||
storageLabel: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should omit a storage label set by non-admin users', async () => {
|
||||
userMock.update.mockResolvedValue(userStub.user1);
|
||||
await sut.update(userStub.user1, { id: userStub.user1.id, storageLabel: 'admin' });
|
||||
await sut.update({ user: userStub.user1 }, { id: userStub.user1.id, storageLabel: 'admin' });
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { id: userStub.user1.id });
|
||||
});
|
||||
|
||||
|
@ -145,10 +148,13 @@ describe(UserService.name, () => {
|
|||
id: 'not_immich_auth_user_id',
|
||||
});
|
||||
|
||||
const result = sut.update(userStub.user1, {
|
||||
id: 'not_immich_auth_user_id',
|
||||
password: 'I take over your account now',
|
||||
});
|
||||
const result = sut.update(
|
||||
{ user: userStub.user1 },
|
||||
{
|
||||
id: 'not_immich_auth_user_id',
|
||||
password: 'I take over your account now',
|
||||
},
|
||||
);
|
||||
await expect(result).rejects.toBeInstanceOf(ForbiddenException);
|
||||
});
|
||||
|
||||
|
@ -158,7 +164,7 @@ describe(UserService.name, () => {
|
|||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
userMock.update.mockResolvedValue(userStub.user1);
|
||||
|
||||
await sut.update(userStub.user1, dto);
|
||||
await sut.update({ user: userStub.user1 }, dto);
|
||||
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
|
||||
id: 'user-id',
|
||||
|
@ -172,7 +178,7 @@ describe(UserService.name, () => {
|
|||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
userMock.getByEmail.mockResolvedValue(userStub.admin);
|
||||
|
||||
await expect(sut.update(userStub.user1, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.update({ user: userStub.user1 }, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(userMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -183,7 +189,7 @@ describe(UserService.name, () => {
|
|||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
userMock.getByStorageLabel.mockResolvedValue(userStub.admin);
|
||||
|
||||
await expect(sut.update(userStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.update(authStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(userMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -195,7 +201,7 @@ describe(UserService.name, () => {
|
|||
};
|
||||
|
||||
when(userMock.update).calledWith(userStub.user1.id, update).mockResolvedValueOnce(userStub.user1);
|
||||
await sut.update(userStub.admin, update);
|
||||
await sut.update(authStub.admin, update);
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
|
||||
id: 'user-id',
|
||||
shouldChangePassword: true,
|
||||
|
@ -205,7 +211,7 @@ describe(UserService.name, () => {
|
|||
it('update user information should throw error if user not found', async () => {
|
||||
when(userMock.get).calledWith(userStub.user1.id, {}).mockResolvedValueOnce(null);
|
||||
|
||||
const result = sut.update(userStub.admin, {
|
||||
const result = sut.update(authStub.admin, {
|
||||
id: userStub.user1.id,
|
||||
shouldChangePassword: true,
|
||||
});
|
||||
|
@ -218,7 +224,7 @@ describe(UserService.name, () => {
|
|||
|
||||
when(userMock.update).calledWith(userStub.admin.id, dto).mockResolvedValueOnce(userStub.admin);
|
||||
|
||||
await sut.update(userStub.admin, dto);
|
||||
await sut.update(authStub.admin, dto);
|
||||
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.admin.id, dto);
|
||||
});
|
||||
|
@ -228,7 +234,7 @@ describe(UserService.name, () => {
|
|||
|
||||
when(userMock.get).calledWith(userStub.user1.id, {}).mockResolvedValueOnce(userStub.user1);
|
||||
|
||||
await expect(sut.update(userStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.update(authStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -239,11 +245,6 @@ describe(UserService.name, () => {
|
|||
expect(userMock.restore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should require an admin', async () => {
|
||||
when(userMock.get).calledWith(userStub.admin.id, { withDeleted: true }).mockResolvedValue(userStub.admin);
|
||||
await expect(sut.restore(authStub.user1, userStub.admin.id)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
});
|
||||
|
||||
it('should restore an user', async () => {
|
||||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
userMock.restore.mockResolvedValue(userStub.user1);
|
||||
|
@ -267,7 +268,7 @@ describe(UserService.name, () => {
|
|||
});
|
||||
|
||||
it('should require the auth user be an admin', async () => {
|
||||
await expect(sut.delete(authStub.user1, authStub.admin.id)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(sut.delete(authStub.user1, authStub.admin.user.id)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
|
||||
expect(userMock.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -276,7 +277,7 @@ describe(UserService.name, () => {
|
|||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
userMock.delete.mockResolvedValue(userStub.user1);
|
||||
|
||||
await expect(sut.delete(userStub.admin, userStub.user1.id)).resolves.toEqual(mapUser(userStub.user1));
|
||||
await expect(sut.delete(authStub.admin, userStub.user1.id)).resolves.toEqual(mapUser(userStub.user1));
|
||||
expect(userMock.get).toHaveBeenCalledWith(userStub.user1.id, {});
|
||||
expect(userMock.delete).toHaveBeenCalledWith(userStub.user1);
|
||||
});
|
||||
|
@ -323,7 +324,7 @@ describe(UserService.name, () => {
|
|||
const file = { path: '/profile/path' } as Express.Multer.File;
|
||||
userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path });
|
||||
|
||||
await expect(sut.createProfileImage(userStub.admin, file)).rejects.toThrowError(BadRequestException);
|
||||
await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(BadRequestException);
|
||||
});
|
||||
|
||||
it('should throw an error if the user profile could not be updated with the new image', async () => {
|
||||
|
@ -331,7 +332,7 @@ describe(UserService.name, () => {
|
|||
userMock.get.mockResolvedValue(userStub.profilePath);
|
||||
userMock.update.mockRejectedValue(new InternalServerErrorException('mocked error'));
|
||||
|
||||
await expect(sut.createProfileImage(userStub.admin, file)).rejects.toThrowError(InternalServerErrorException);
|
||||
await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(InternalServerErrorException);
|
||||
});
|
||||
|
||||
it('should delete the previous profile image', async () => {
|
||||
|
@ -340,7 +341,7 @@ describe(UserService.name, () => {
|
|||
const files = [userStub.profilePath.profileImagePath];
|
||||
userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path });
|
||||
|
||||
await sut.createProfileImage(userStub.admin, file);
|
||||
await sut.createProfileImage(authStub.admin, file);
|
||||
await expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.DELETE_FILES, data: { files } }]]);
|
||||
});
|
||||
|
||||
|
@ -349,7 +350,7 @@ describe(UserService.name, () => {
|
|||
userMock.get.mockResolvedValue(userStub.admin);
|
||||
userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path });
|
||||
|
||||
await sut.createProfileImage(userStub.admin, file);
|
||||
await sut.createProfileImage(authStub.admin, file);
|
||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -358,7 +359,7 @@ describe(UserService.name, () => {
|
|||
it('should send an http error has no profile image', async () => {
|
||||
userMock.get.mockResolvedValue(userStub.admin);
|
||||
|
||||
await expect(sut.deleteProfileImage(userStub.admin)).rejects.toBeInstanceOf(BadRequestException);
|
||||
await expect(sut.deleteProfileImage(authStub.admin)).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -366,7 +367,7 @@ describe(UserService.name, () => {
|
|||
userMock.get.mockResolvedValue(userStub.profilePath);
|
||||
const files = [userStub.profilePath.profileImagePath];
|
||||
|
||||
await sut.deleteProfileImage(userStub.admin);
|
||||
await sut.deleteProfileImage(authStub.admin);
|
||||
await expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.DELETE_FILES, data: { files } }]]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { UserEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, ForbiddenException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AuthDto } from '../auth';
|
||||
import { IEntityJob, JobName } from '../job';
|
||||
import {
|
||||
IAlbumRepository,
|
||||
|
@ -36,7 +36,7 @@ export class UserService {
|
|||
this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository);
|
||||
}
|
||||
|
||||
async getAll(authUser: AuthUserDto, isAll: boolean): Promise<UserResponseDto[]> {
|
||||
async getAll(auth: AuthDto, isAll: boolean): Promise<UserResponseDto[]> {
|
||||
const users = await this.userRepository.getList({ withDeleted: !isAll });
|
||||
return users.map(mapUser);
|
||||
}
|
||||
|
@ -50,24 +50,20 @@ export class UserService {
|
|||
return mapUser(user);
|
||||
}
|
||||
|
||||
getMe(authUser: AuthUserDto): Promise<UserResponseDto> {
|
||||
return this.findOrFail(authUser.id, {}).then(mapUser);
|
||||
getMe(auth: AuthDto): Promise<UserResponseDto> {
|
||||
return this.findOrFail(auth.user.id, {}).then(mapUser);
|
||||
}
|
||||
|
||||
create(createUserDto: CreateUserDto): Promise<UserResponseDto> {
|
||||
return this.userCore.createUser(createUserDto).then(mapUser);
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, dto: UpdateUserDto): Promise<UserResponseDto> {
|
||||
async update(auth: AuthDto, dto: UpdateUserDto): Promise<UserResponseDto> {
|
||||
await this.findOrFail(dto.id, {});
|
||||
return this.userCore.updateUser(authUser, dto.id, dto).then(mapUser);
|
||||
return this.userCore.updateUser(auth.user, dto.id, dto).then(mapUser);
|
||||
}
|
||||
|
||||
async delete(authUser: AuthUserDto, id: string): Promise<UserResponseDto> {
|
||||
if (!authUser.isAdmin) {
|
||||
throw new ForbiddenException('Unauthorized');
|
||||
}
|
||||
|
||||
async delete(auth: AuthDto, id: string): Promise<UserResponseDto> {
|
||||
const user = await this.findOrFail(id, {});
|
||||
if (user.isAdmin) {
|
||||
throw new ForbiddenException('Cannot delete admin user');
|
||||
|
@ -78,35 +74,28 @@ export class UserService {
|
|||
return this.userRepository.delete(user).then(mapUser);
|
||||
}
|
||||
|
||||
async restore(authUser: AuthUserDto, id: string): Promise<UserResponseDto> {
|
||||
if (!authUser.isAdmin) {
|
||||
throw new ForbiddenException('Unauthorized');
|
||||
}
|
||||
|
||||
async restore(auth: AuthDto, id: string): Promise<UserResponseDto> {
|
||||
let user = await this.findOrFail(id, { withDeleted: true });
|
||||
user = await this.userRepository.restore(user);
|
||||
await this.albumRepository.restoreAll(id);
|
||||
return mapUser(user);
|
||||
}
|
||||
|
||||
async createProfileImage(
|
||||
authUser: AuthUserDto,
|
||||
fileInfo: Express.Multer.File,
|
||||
): Promise<CreateProfileImageResponseDto> {
|
||||
const { profileImagePath: oldpath } = await this.findOrFail(authUser.id, { withDeleted: false });
|
||||
const updatedUser = await this.userRepository.update(authUser.id, { profileImagePath: fileInfo.path });
|
||||
async createProfileImage(auth: AuthDto, fileInfo: Express.Multer.File): Promise<CreateProfileImageResponseDto> {
|
||||
const { profileImagePath: oldpath } = await this.findOrFail(auth.user.id, { withDeleted: false });
|
||||
const updatedUser = await this.userRepository.update(auth.user.id, { profileImagePath: fileInfo.path });
|
||||
if (oldpath !== '') {
|
||||
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [oldpath] } });
|
||||
}
|
||||
return mapCreateProfileImageResponse(updatedUser.id, updatedUser.profileImagePath);
|
||||
}
|
||||
|
||||
async deleteProfileImage(authUser: AuthUserDto): Promise<void> {
|
||||
const user = await this.findOrFail(authUser.id, { withDeleted: false });
|
||||
async deleteProfileImage(auth: AuthDto): Promise<void> {
|
||||
const user = await this.findOrFail(auth.user.id, { withDeleted: false });
|
||||
if (user.profileImagePath === '') {
|
||||
throw new BadRequestException("Can't delete a missing profile Image");
|
||||
}
|
||||
await this.userRepository.update(authUser.id, { profileImagePath: '' });
|
||||
await this.userRepository.update(auth.user.id, { profileImagePath: '' });
|
||||
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [user.profileImagePath] } });
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { AuthUserDto } from '@app/domain';
|
||||
import { AuthDto } from '@app/domain';
|
||||
import { UserEntity } from '@app/infra/entities';
|
||||
|
||||
export const CLI_USER: AuthUserDto = {
|
||||
id: 'cli',
|
||||
email: 'cli@immich.app',
|
||||
isAdmin: true,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
export const CLI_USER: AuthDto = {
|
||||
user: {
|
||||
id: 'cli',
|
||||
email: 'cli@immich.app',
|
||||
isAdmin: true,
|
||||
} as UserEntity,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetResponseDto, AuthUserDto } from '@app/domain';
|
||||
import { AssetResponseDto, AuthDto } from '@app/domain';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
|
@ -16,7 +16,7 @@ import {
|
|||
} from '@nestjs/common';
|
||||
import { ApiBody, ApiConsumes, ApiHeader, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { Response as Res } from 'express';
|
||||
import { AuthUser, Authenticated, SharedLinkRoute } from '../../app.guard';
|
||||
import { Auth, Authenticated, SharedLinkRoute } from '../../app.guard';
|
||||
import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto';
|
||||
import { FileUploadInterceptor, ImmichFile, Route, mapToUploadFile } from '../../interceptors';
|
||||
import FileNotEmptyValidator from '../validation/file-not-empty-validator';
|
||||
|
@ -55,7 +55,7 @@ export class AssetController {
|
|||
type: CreateAssetDto,
|
||||
})
|
||||
async uploadFile(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator(['assetData'])] })) files: UploadFiles,
|
||||
@Body(new ValidationPipe({ transform: true })) dto: CreateAssetDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
|
@ -73,7 +73,7 @@ export class AssetController {
|
|||
sidecarFile = mapToUploadFile(_sidecarFile);
|
||||
}
|
||||
|
||||
const responseDto = await this.assetService.uploadFile(authUser, dto, file, livePhotoFile, sidecarFile);
|
||||
const responseDto = await this.assetService.uploadFile(auth, dto, file, livePhotoFile, sidecarFile);
|
||||
if (responseDto.duplicate) {
|
||||
res.status(HttpStatus.OK);
|
||||
}
|
||||
|
@ -89,12 +89,12 @@ export class AssetController {
|
|||
},
|
||||
})
|
||||
async serveFile(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Response() res: Res,
|
||||
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
) {
|
||||
await this.assetService.serveFile(authUser, id, query, res);
|
||||
await this.assetService.serveFile(auth, id, query, res);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
|
@ -106,27 +106,27 @@ export class AssetController {
|
|||
},
|
||||
})
|
||||
async getAssetThumbnail(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Response() res: Res,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
|
||||
) {
|
||||
await this.assetService.serveThumbnail(authUser, id, query, res);
|
||||
await this.assetService.serveThumbnail(auth, id, query, res);
|
||||
}
|
||||
|
||||
@Get('/curated-objects')
|
||||
getCuratedObjects(@AuthUser() authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
||||
return this.assetService.getCuratedObject(authUser);
|
||||
getCuratedObjects(@Auth() auth: AuthDto): Promise<CuratedObjectsResponseDto[]> {
|
||||
return this.assetService.getCuratedObject(auth);
|
||||
}
|
||||
|
||||
@Get('/curated-locations')
|
||||
getCuratedLocations(@AuthUser() authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
||||
return this.assetService.getCuratedLocation(authUser);
|
||||
getCuratedLocations(@Auth() auth: AuthDto): Promise<CuratedLocationsResponseDto[]> {
|
||||
return this.assetService.getCuratedLocation(auth);
|
||||
}
|
||||
|
||||
@Get('/search-terms')
|
||||
getAssetSearchTerms(@AuthUser() authUser: AuthUserDto): Promise<string[]> {
|
||||
return this.assetService.getAssetSearchTerm(authUser);
|
||||
getAssetSearchTerms(@Auth() auth: AuthDto): Promise<string[]> {
|
||||
return this.assetService.getAssetSearchTerm(auth);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,10 +140,10 @@ export class AssetController {
|
|||
schema: { type: 'string' },
|
||||
})
|
||||
getAllAssets(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Query(new ValidationPipe({ transform: true })) dto: AssetSearchDto,
|
||||
): Promise<AssetResponseDto[]> {
|
||||
return this.assetService.getAllAssets(authUser, dto);
|
||||
return this.assetService.getAllAssets(auth, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,8 +151,8 @@ export class AssetController {
|
|||
*/
|
||||
@Get('/:deviceId')
|
||||
@ApiOperation({ deprecated: true, summary: 'Use /asset/device/:deviceId instead - Remove in 1.92 release' })
|
||||
getUserAssetsByDeviceId(@AuthUser() authUser: AuthUserDto, @Param() { deviceId }: DeviceIdDto) {
|
||||
return this.assetService.getUserAssetsByDeviceId(authUser, deviceId);
|
||||
getUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) {
|
||||
return this.assetService.getUserAssetsByDeviceId(auth, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,8 +160,8 @@ export class AssetController {
|
|||
*/
|
||||
@SharedLinkRoute()
|
||||
@Get('/assetById/:id')
|
||||
getAssetById(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto> {
|
||||
return this.assetService.getAssetById(authUser, id) as Promise<AssetResponseDto>;
|
||||
getAssetById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto> {
|
||||
return this.assetService.getAssetById(auth, id) as Promise<AssetResponseDto>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,10 +170,10 @@ export class AssetController {
|
|||
@Post('/exist')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
checkExistingAssets(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Body(ValidationPipe) dto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
return this.assetService.checkExistingAssets(authUser, dto);
|
||||
return this.assetService.checkExistingAssets(auth, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -182,9 +182,9 @@ export class AssetController {
|
|||
@Post('/bulk-upload-check')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
checkBulkUpload(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Body(ValidationPipe) dto: AssetBulkUploadCheckDto,
|
||||
): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
return this.assetService.bulkUploadCheck(authUser, dto);
|
||||
return this.assetService.bulkUploadCheck(auth, dto);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AuthUserDto, IJobRepository, JobName, mimeTypes, UploadFile } from '@app/domain';
|
||||
import { AuthDto, IJobRepository, JobName, mimeTypes, UploadFile } from '@app/domain';
|
||||
import { AssetEntity } from '@app/infra/entities';
|
||||
import { parse } from 'node:path';
|
||||
import { IAssetRepository } from './asset-repository';
|
||||
|
@ -11,14 +11,14 @@ export class AssetCore {
|
|||
) {}
|
||||
|
||||
async create(
|
||||
authUser: AuthUserDto,
|
||||
auth: AuthDto,
|
||||
dto: CreateAssetDto & { libraryId: string },
|
||||
file: UploadFile,
|
||||
livePhotoAssetId?: string,
|
||||
sidecarPath?: string,
|
||||
): Promise<AssetEntity> {
|
||||
const asset = await this.repository.create({
|
||||
ownerId: authUser.id,
|
||||
ownerId: auth.user.id,
|
||||
libraryId: dto.libraryId,
|
||||
|
||||
checksum: file.checksum,
|
||||
|
|
|
@ -226,7 +226,7 @@ describe('AssetService', () => {
|
|||
],
|
||||
});
|
||||
|
||||
expect(assetRepositoryMock.getAssetsByChecksums).toHaveBeenCalledWith(authStub.admin.id, [file1, file2]);
|
||||
expect(assetRepositoryMock.getAssetsByChecksums).toHaveBeenCalledWith(authStub.admin.user.id, [file1, file2]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -235,7 +235,10 @@ describe('AssetService', () => {
|
|||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set([assetStub.image.id]));
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.user.id,
|
||||
new Set([assetStub.image.id]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow shared link access', async () => {
|
||||
|
@ -243,7 +246,7 @@ describe('AssetService', () => {
|
|||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||
await sut.getAssetById(authStub.adminSharedLink, assetStub.image.id);
|
||||
expect(accessMock.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||
authStub.adminSharedLink.sharedLinkId,
|
||||
authStub.adminSharedLink.sharedLink?.id,
|
||||
new Set([assetStub.image.id]),
|
||||
);
|
||||
});
|
||||
|
@ -253,7 +256,7 @@ describe('AssetService', () => {
|
|||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
||||
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.id,
|
||||
authStub.admin.user.id,
|
||||
new Set([assetStub.image.id]),
|
||||
);
|
||||
});
|
||||
|
@ -262,7 +265,10 @@ describe('AssetService', () => {
|
|||
accessMock.asset.checkAlbumAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
||||
expect(accessMock.asset.checkAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, new Set([assetStub.image.id]));
|
||||
expect(accessMock.asset.checkAlbumAccess).toHaveBeenCalledWith(
|
||||
authStub.admin.user.id,
|
||||
new Set([assetStub.image.id]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error for no access', async () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
AccessCore,
|
||||
AssetResponseDto,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
getLivePhotoMotionFilename,
|
||||
IAccessRepository,
|
||||
IJobRepository,
|
||||
|
@ -65,7 +65,7 @@ export class AssetService {
|
|||
}
|
||||
|
||||
public async uploadFile(
|
||||
authUser: AuthUserDto,
|
||||
auth: AuthDto,
|
||||
dto: CreateAssetDto,
|
||||
file: UploadFile,
|
||||
livePhotoFile?: UploadFile,
|
||||
|
@ -81,15 +81,15 @@ export class AssetService {
|
|||
let livePhotoAsset: AssetEntity | null = null;
|
||||
|
||||
try {
|
||||
const libraryId = await this.getLibraryId(authUser, dto.libraryId);
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPLOAD, libraryId);
|
||||
const libraryId = await this.getLibraryId(auth, dto.libraryId);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_UPLOAD, libraryId);
|
||||
if (livePhotoFile) {
|
||||
const livePhotoDto = { ...dto, assetType: AssetType.VIDEO, isVisible: false, libraryId };
|
||||
livePhotoAsset = await this.assetCore.create(authUser, livePhotoDto, livePhotoFile);
|
||||
livePhotoAsset = await this.assetCore.create(auth, livePhotoDto, livePhotoFile);
|
||||
}
|
||||
|
||||
const asset = await this.assetCore.create(
|
||||
authUser,
|
||||
auth,
|
||||
{ ...dto, libraryId },
|
||||
file,
|
||||
livePhotoAsset?.id,
|
||||
|
@ -107,7 +107,7 @@ export class AssetService {
|
|||
// handle duplicates with a success response
|
||||
if (error instanceof QueryFailedError && (error as any).constraint === ASSET_CHECKSUM_CONSTRAINT) {
|
||||
const checksums = [file.checksum, livePhotoFile?.checksum].filter((checksum): checksum is Buffer => !!checksum);
|
||||
const [duplicate] = await this._assetRepository.getAssetsByChecksums(authUser.id, checksums);
|
||||
const [duplicate] = await this._assetRepository.getAssetsByChecksums(auth.user.id, checksums);
|
||||
return { id: duplicate.id, duplicate: true };
|
||||
}
|
||||
|
||||
|
@ -116,33 +116,29 @@ export class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
||||
return this._assetRepository.getAllByDeviceId(authUser.id, deviceId);
|
||||
public async getUserAssetsByDeviceId(auth: AuthDto, deviceId: string) {
|
||||
return this._assetRepository.getAllByDeviceId(auth.user.id, deviceId);
|
||||
}
|
||||
|
||||
public async getAllAssets(authUser: AuthUserDto, dto: AssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
const userId = dto.userId || authUser.id;
|
||||
await this.access.requirePermission(authUser, Permission.TIMELINE_READ, userId);
|
||||
public async getAllAssets(auth: AuthDto, dto: AssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
const userId = dto.userId || auth.user.id;
|
||||
await this.access.requirePermission(auth, Permission.TIMELINE_READ, userId);
|
||||
const assets = await this._assetRepository.getAllByUserId(userId, dto);
|
||||
return assets.map((asset) => mapAsset(asset));
|
||||
}
|
||||
|
||||
public async getAssetById(
|
||||
authUser: AuthUserDto,
|
||||
assetId: string,
|
||||
): Promise<AssetResponseDto | SanitizedAssetResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_READ, assetId);
|
||||
public async getAssetById(auth: AuthDto, assetId: string): Promise<AssetResponseDto | SanitizedAssetResponseDto> {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_READ, assetId);
|
||||
|
||||
const includeMetadata = this.getExifPermission(authUser);
|
||||
const asset = await this._assetRepository.getById(assetId);
|
||||
if (includeMetadata) {
|
||||
if (!auth.sharedLink || auth.sharedLink?.showExif) {
|
||||
const data = mapAsset(asset, { withStack: true });
|
||||
|
||||
if (data.ownerId !== authUser.id) {
|
||||
if (data.ownerId !== auth.user.id) {
|
||||
data.people = [];
|
||||
}
|
||||
|
||||
if (authUser.isPublicUser) {
|
||||
if (auth.sharedLink) {
|
||||
delete data.owner;
|
||||
}
|
||||
|
||||
|
@ -152,8 +148,8 @@ export class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
async serveThumbnail(authUser: AuthUserDto, assetId: string, query: GetAssetThumbnailDto, res: Res) {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_VIEW, assetId);
|
||||
async serveThumbnail(auth: AuthDto, assetId: string, query: GetAssetThumbnailDto, res: Res) {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_VIEW, assetId);
|
||||
|
||||
const asset = await this._assetRepository.get(assetId);
|
||||
if (!asset) {
|
||||
|
@ -172,16 +168,16 @@ export class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
public async serveFile(authUser: AuthUserDto, assetId: string, query: ServeFileDto, res: Res) {
|
||||
public async serveFile(auth: AuthDto, assetId: string, query: ServeFileDto, res: Res) {
|
||||
// this is not quite right as sometimes this returns the original still
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_VIEW, assetId);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_VIEW, assetId);
|
||||
|
||||
const asset = await this._assetRepository.getById(assetId);
|
||||
if (!asset) {
|
||||
throw new NotFoundException('Asset does not exist');
|
||||
}
|
||||
|
||||
const allowOriginalFile = !!(!authUser.isPublicUser || authUser.isAllowDownload);
|
||||
const allowOriginalFile = !!(!auth.sharedLink || auth.sharedLink?.allowDownload);
|
||||
|
||||
const filepath =
|
||||
asset.type === AssetType.IMAGE
|
||||
|
@ -191,10 +187,10 @@ export class AssetService {
|
|||
await this.sendFile(res, filepath);
|
||||
}
|
||||
|
||||
async getAssetSearchTerm(authUser: AuthUserDto): Promise<string[]> {
|
||||
async getAssetSearchTerm(auth: AuthDto): Promise<string[]> {
|
||||
const possibleSearchTerm = new Set<string>();
|
||||
|
||||
const rows = await this._assetRepository.getSearchPropertiesByUserId(authUser.id);
|
||||
const rows = await this._assetRepository.getSearchPropertiesByUserId(auth.user.id);
|
||||
rows.forEach((row: SearchPropertiesDto) => {
|
||||
// tags
|
||||
row.tags?.map((tag: string) => possibleSearchTerm.add(tag?.toLowerCase()));
|
||||
|
@ -224,24 +220,24 @@ export class AssetService {
|
|||
return Array.from(possibleSearchTerm).filter((x) => x != null && x != '');
|
||||
}
|
||||
|
||||
async getCuratedLocation(authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
||||
return this._assetRepository.getLocationsByUserId(authUser.id);
|
||||
async getCuratedLocation(auth: AuthDto): Promise<CuratedLocationsResponseDto[]> {
|
||||
return this._assetRepository.getLocationsByUserId(auth.user.id);
|
||||
}
|
||||
|
||||
async getCuratedObject(authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
||||
return this._assetRepository.getDetectedObjectsByUserId(authUser.id);
|
||||
async getCuratedObject(auth: AuthDto): Promise<CuratedObjectsResponseDto[]> {
|
||||
return this._assetRepository.getDetectedObjectsByUserId(auth.user.id);
|
||||
}
|
||||
|
||||
async checkExistingAssets(
|
||||
authUser: AuthUserDto,
|
||||
auth: AuthDto,
|
||||
checkExistingAssetsDto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
return {
|
||||
existingIds: await this._assetRepository.getExistingAssets(authUser.id, checkExistingAssetsDto),
|
||||
existingIds: await this._assetRepository.getExistingAssets(auth.user.id, checkExistingAssetsDto),
|
||||
};
|
||||
}
|
||||
|
||||
async bulkUploadCheck(authUser: AuthUserDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
async bulkUploadCheck(auth: AuthDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
// support base64 and hex checksums
|
||||
for (const asset of dto.assets) {
|
||||
if (asset.checksum.length === 28) {
|
||||
|
@ -250,7 +246,7 @@ export class AssetService {
|
|||
}
|
||||
|
||||
const checksums: Buffer[] = dto.assets.map((asset) => Buffer.from(asset.checksum, 'hex'));
|
||||
const results = await this._assetRepository.getAssetsByChecksums(authUser.id, checksums);
|
||||
const results = await this._assetRepository.getAssetsByChecksums(auth.user.id, checksums);
|
||||
const checksumMap: Record<string, string> = {};
|
||||
|
||||
for (const { id, checksum } of results) {
|
||||
|
@ -279,10 +275,6 @@ export class AssetService {
|
|||
};
|
||||
}
|
||||
|
||||
getExifPermission(authUser: AuthUserDto) {
|
||||
return !authUser.isPublicUser || authUser.isShowMetadata;
|
||||
}
|
||||
|
||||
private getThumbnailPath(asset: AssetEntity, format: GetAssetThumbnailFormatEnum) {
|
||||
switch (format) {
|
||||
case GetAssetThumbnailFormatEnum.WEBP:
|
||||
|
@ -354,15 +346,15 @@ export class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
private async getLibraryId(authUser: AuthUserDto, libraryId?: string) {
|
||||
private async getLibraryId(auth: AuthDto, libraryId?: string) {
|
||||
if (libraryId) {
|
||||
return libraryId;
|
||||
}
|
||||
|
||||
let library = await this.libraryRepository.getDefaultUploadLibrary(authUser.id);
|
||||
let library = await this.libraryRepository.getDefaultUploadLibrary(auth.user.id);
|
||||
if (!library) {
|
||||
library = await this.libraryRepository.create({
|
||||
ownerId: authUser.id,
|
||||
ownerId: auth.user.id,
|
||||
name: 'Default Library',
|
||||
assets: [],
|
||||
type: LibraryType.UPLOAD,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AuthService, AuthUserDto, IMMICH_API_KEY_NAME, LoginDetails } from '@app/domain';
|
||||
import { AuthDto, AuthService, IMMICH_API_KEY_NAME, LoginDetails } from '@app/domain';
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
|
@ -50,8 +50,8 @@ export const SharedLinkRoute = () =>
|
|||
applyDecorators(SetMetadata(Metadata.SHARED_ROUTE, true), ApiQuery({ name: 'key', type: String, required: false }));
|
||||
export const AdminRoute = (value = true) => SetMetadata(Metadata.ADMIN_ROUTE, value);
|
||||
|
||||
export const AuthUser = createParamDecorator((data, ctx: ExecutionContext): AuthUserDto => {
|
||||
return ctx.switchToHttp().getRequest<{ user: AuthUserDto }>().user;
|
||||
export const Auth = createParamDecorator((data, ctx: ExecutionContext): AuthDto => {
|
||||
return ctx.switchToHttp().getRequest<{ user: AuthDto }>().user;
|
||||
});
|
||||
|
||||
export const GetLoginDetails = createParamDecorator((data, ctx: ExecutionContext): LoginDetails => {
|
||||
|
@ -67,7 +67,7 @@ export const GetLoginDetails = createParamDecorator((data, ctx: ExecutionContext
|
|||
});
|
||||
|
||||
export interface AuthRequest extends Request {
|
||||
user?: AuthUserDto;
|
||||
user?: AuthDto;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
@ -93,12 +93,12 @@ export class AppGuard implements CanActivate {
|
|||
const req = context.switchToHttp().getRequest<AuthRequest>();
|
||||
|
||||
const authDto = await this.authService.validate(req.headers, req.query as Record<string, string>);
|
||||
if (authDto.isPublicUser && !isSharedRoute) {
|
||||
if (authDto.sharedLink && !isSharedRoute) {
|
||||
this.logger.warn(`Denied access to non-shared route: ${req.path}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAdminRoute && !authDto.isAdmin) {
|
||||
if (isAdminRoute && !authDto.user.isAdmin) {
|
||||
this.logger.warn(`Denied access to admin only route: ${req.path}`);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AuthUserDto } from '@app/domain';
|
||||
import { AuthDto } from '@app/domain';
|
||||
import {
|
||||
ActivityDto,
|
||||
ActivitySearchDto,
|
||||
|
@ -10,7 +10,7 @@ import {
|
|||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Query, Res } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { AuthUser, Authenticated } from '../app.guard';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -22,22 +22,22 @@ export class ActivityController {
|
|||
constructor(private service: ActivityService) {}
|
||||
|
||||
@Get()
|
||||
getActivities(@AuthUser() authUser: AuthUserDto, @Query() dto: ActivitySearchDto): Promise<ResponseDto[]> {
|
||||
return this.service.getAll(authUser, dto);
|
||||
getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ResponseDto[]> {
|
||||
return this.service.getAll(auth, dto);
|
||||
}
|
||||
|
||||
@Get('statistics')
|
||||
getActivityStatistics(@AuthUser() authUser: AuthUserDto, @Query() dto: ActivityDto): Promise<StatsResponseDto> {
|
||||
return this.service.getStatistics(authUser, dto);
|
||||
getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<StatsResponseDto> {
|
||||
return this.service.getStatistics(auth, dto);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createActivity(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Body() dto: CreateDto,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
): Promise<ResponseDto> {
|
||||
const { duplicate, value } = await this.service.create(authUser, dto);
|
||||
const { duplicate, value } = await this.service.create(auth, dto);
|
||||
if (duplicate) {
|
||||
res.status(HttpStatus.OK);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export class ActivityController {
|
|||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteActivity(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(authUser, id);
|
||||
deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
AlbumInfoDto,
|
||||
AlbumResponseDto,
|
||||
AlbumService,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
BulkIdResponseDto,
|
||||
BulkIdsDto,
|
||||
CreateAlbumDto as CreateDto,
|
||||
|
@ -14,7 +14,7 @@ import {
|
|||
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ParseMeUUIDPipe } from '../api-v1/validation/parse-me-uuid-pipe';
|
||||
import { AuthUser, Authenticated, SharedLinkRoute } from '../app.guard';
|
||||
import { Auth, Authenticated, SharedLinkRoute } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -26,78 +26,78 @@ export class AlbumController {
|
|||
constructor(private service: AlbumService) {}
|
||||
|
||||
@Get('count')
|
||||
getAlbumCount(@AuthUser() authUser: AuthUserDto): Promise<AlbumCountResponseDto> {
|
||||
return this.service.getCount(authUser);
|
||||
getAlbumCount(@Auth() auth: AuthDto): Promise<AlbumCountResponseDto> {
|
||||
return this.service.getCount(auth);
|
||||
}
|
||||
|
||||
@Get()
|
||||
getAllAlbums(@AuthUser() authUser: AuthUserDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||
return this.service.getAll(authUser, query);
|
||||
getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||
return this.service.getAll(auth, query);
|
||||
}
|
||||
|
||||
@Post()
|
||||
createAlbum(@AuthUser() authUser: AuthUserDto, @Body() dto: CreateDto): Promise<AlbumResponseDto> {
|
||||
return this.service.create(authUser, dto);
|
||||
createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateDto): Promise<AlbumResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Get(':id')
|
||||
getAlbumInfo(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Query() dto: AlbumInfoDto,
|
||||
): Promise<AlbumResponseDto> {
|
||||
return this.service.get(authUser, id, dto);
|
||||
return this.service.get(auth, id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
updateAlbumInfo(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: UpdateDto,
|
||||
): Promise<AlbumResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
deleteAlbum(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.delete(authUser, id);
|
||||
deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Put(':id/assets')
|
||||
addAssetsToAlbum(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: BulkIdsDto,
|
||||
): Promise<BulkIdResponseDto[]> {
|
||||
return this.service.addAssets(authUser, id, dto);
|
||||
return this.service.addAssets(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id/assets')
|
||||
removeAssetFromAlbum(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Body() dto: BulkIdsDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
): Promise<BulkIdResponseDto[]> {
|
||||
return this.service.removeAssets(authUser, id, dto);
|
||||
return this.service.removeAssets(auth, id, dto);
|
||||
}
|
||||
|
||||
@Put(':id/users')
|
||||
addUsersToAlbum(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AddUsersDto,
|
||||
): Promise<AlbumResponseDto> {
|
||||
return this.service.addUsers(authUser, id, dto);
|
||||
return this.service.addUsers(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id/user/:userId')
|
||||
removeUserFromAlbum(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
|
||||
) {
|
||||
return this.service.removeUser(authUser, id, userId);
|
||||
return this.service.removeUser(auth, id, userId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import {
|
|||
APIKeyResponseDto,
|
||||
APIKeyService,
|
||||
APIKeyUpdateDto,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
} from '@app/domain';
|
||||
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUser, Authenticated } from '../app.guard';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -20,31 +20,31 @@ export class APIKeyController {
|
|||
constructor(private service: APIKeyService) {}
|
||||
|
||||
@Post()
|
||||
createApiKey(@AuthUser() authUser: AuthUserDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||
return this.service.create(authUser, dto);
|
||||
createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
getApiKeys(@AuthUser() authUser: AuthUserDto): Promise<APIKeyResponseDto[]> {
|
||||
return this.service.getAll(authUser);
|
||||
getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
|
||||
return this.service.getAll(auth);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getApiKey(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
|
||||
return this.service.getById(authUser, id);
|
||||
getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
|
||||
return this.service.getById(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
updateApiKey(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: APIKeyUpdateDto,
|
||||
): Promise<APIKeyResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
deleteApiKey(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(authUser, id);
|
||||
deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
AssetService,
|
||||
AssetStatsDto,
|
||||
AssetStatsResponseDto,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
BulkIdsDto,
|
||||
DownloadInfoDto,
|
||||
DownloadResponseDto,
|
||||
|
@ -39,7 +39,7 @@ import {
|
|||
} from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { DeviceIdDto } from '../api-v1/asset/dto/device-id.dto';
|
||||
import { AuthUser, Authenticated, SharedLinkRoute } from '../app.guard';
|
||||
import { Auth, Authenticated, SharedLinkRoute } from '../app.guard';
|
||||
import { UseValidation, asStreamableFile } from '../app.utils';
|
||||
import { Route } from '../interceptors';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
@ -52,8 +52,8 @@ export class AssetsController {
|
|||
constructor(private service: AssetService) {}
|
||||
|
||||
@Get()
|
||||
searchAssets(@AuthUser() authUser: AuthUserDto, @Query() dto: AssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.search(authUser, dto);
|
||||
searchAssets(@Auth() auth: AuthDto, @Query() dto: AssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.search(auth, dto);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,115 +65,111 @@ export class AssetController {
|
|||
constructor(private service: AssetService) {}
|
||||
|
||||
@Get('map-marker')
|
||||
getMapMarkers(@AuthUser() authUser: AuthUserDto, @Query() options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
||||
return this.service.getMapMarkers(authUser, options);
|
||||
getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
||||
return this.service.getMapMarkers(auth, options);
|
||||
}
|
||||
|
||||
@Get('memory-lane')
|
||||
getMemoryLane(@AuthUser() authUser: AuthUserDto, @Query() dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
|
||||
return this.service.getMemoryLane(authUser, dto);
|
||||
getMemoryLane(@Auth() auth: AuthDto, @Query() dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
|
||||
return this.service.getMemoryLane(auth, dto);
|
||||
}
|
||||
|
||||
@Get('random')
|
||||
getRandom(@AuthUser() authUser: AuthUserDto, @Query() dto: RandomAssetsDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getRandom(authUser, dto.count ?? 1);
|
||||
getRandom(@Auth() auth: AuthDto, @Query() dto: RandomAssetsDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getRandom(auth, dto.count ?? 1);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Post('download/info')
|
||||
getDownloadInfo(@AuthUser() authUser: AuthUserDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> {
|
||||
return this.service.getDownloadInfo(authUser, dto);
|
||||
getDownloadInfo(@Auth() auth: AuthDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> {
|
||||
return this.service.getDownloadInfo(auth, dto);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Post('download/archive')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
downloadArchive(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetIdsDto): Promise<StreamableFile> {
|
||||
return this.service.downloadArchive(authUser, dto).then(asStreamableFile);
|
||||
downloadArchive(@Auth() auth: AuthDto, @Body() dto: AssetIdsDto): Promise<StreamableFile> {
|
||||
return this.service.downloadArchive(auth, dto).then(asStreamableFile);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Post('download/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
|
||||
downloadFile(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.downloadFile(authUser, id).then(asStreamableFile);
|
||||
downloadFile(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.downloadFile(auth, id).then(asStreamableFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all asset of a device that are in the database, ID only.
|
||||
*/
|
||||
@Get('/device/:deviceId')
|
||||
getAllUserAssetsByDeviceId(@AuthUser() authUser: AuthUserDto, @Param() { deviceId }: DeviceIdDto) {
|
||||
return this.service.getUserAssetsByDeviceId(authUser, deviceId);
|
||||
getAllUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) {
|
||||
return this.service.getUserAssetsByDeviceId(auth, deviceId);
|
||||
}
|
||||
|
||||
@Get('statistics')
|
||||
getAssetStatistics(@AuthUser() authUser: AuthUserDto, @Query() dto: AssetStatsDto): Promise<AssetStatsResponseDto> {
|
||||
return this.service.getStatistics(authUser, dto);
|
||||
getAssetStatistics(@Auth() auth: AuthDto, @Query() dto: AssetStatsDto): Promise<AssetStatsResponseDto> {
|
||||
return this.service.getStatistics(auth, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('time-buckets')
|
||||
getTimeBuckets(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
|
||||
return this.service.getTimeBuckets(authUser, dto);
|
||||
getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
|
||||
return this.service.getTimeBuckets(auth, dto);
|
||||
}
|
||||
|
||||
@Authenticated({ isShared: true })
|
||||
@Get('time-bucket')
|
||||
getTimeBucket(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getTimeBucket(authUser, dto) as Promise<AssetResponseDto[]>;
|
||||
getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getTimeBucket(auth, dto) as Promise<AssetResponseDto[]>;
|
||||
}
|
||||
|
||||
@Post('jobs')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
runAssetJobs(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetJobsDto): Promise<void> {
|
||||
return this.service.run(authUser, dto);
|
||||
runAssetJobs(@Auth() auth: AuthDto, @Body() dto: AssetJobsDto): Promise<void> {
|
||||
return this.service.run(auth, dto);
|
||||
}
|
||||
|
||||
@Put()
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
updateAssets(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetBulkUpdateDto): Promise<void> {
|
||||
return this.service.updateAll(authUser, dto);
|
||||
updateAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise<void> {
|
||||
return this.service.updateAll(auth, dto);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteAssets(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetBulkDeleteDto): Promise<void> {
|
||||
return this.service.deleteAll(authUser, dto);
|
||||
deleteAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkDeleteDto): Promise<void> {
|
||||
return this.service.deleteAll(auth, dto);
|
||||
}
|
||||
|
||||
@Post('restore')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
restoreAssets(@AuthUser() authUser: AuthUserDto, @Body() dto: BulkIdsDto): Promise<void> {
|
||||
return this.service.restoreAll(authUser, dto);
|
||||
restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
|
||||
return this.service.restoreAll(auth, dto);
|
||||
}
|
||||
|
||||
@Post('trash/empty')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
emptyTrash(@AuthUser() authUser: AuthUserDto): Promise<void> {
|
||||
return this.service.handleTrashAction(authUser, TrashAction.EMPTY_ALL);
|
||||
emptyTrash(@Auth() auth: AuthDto): Promise<void> {
|
||||
return this.service.handleTrashAction(auth, TrashAction.EMPTY_ALL);
|
||||
}
|
||||
|
||||
@Post('trash/restore')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
restoreTrash(@AuthUser() authUser: AuthUserDto): Promise<void> {
|
||||
return this.service.handleTrashAction(authUser, TrashAction.RESTORE_ALL);
|
||||
restoreTrash(@Auth() auth: AuthDto): Promise<void> {
|
||||
return this.service.handleTrashAction(auth, TrashAction.RESTORE_ALL);
|
||||
}
|
||||
|
||||
@Put('stack/parent')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
updateStackParent(@AuthUser() authUser: AuthUserDto, @Body() dto: UpdateStackParentDto): Promise<void> {
|
||||
return this.service.updateStackParent(authUser, dto);
|
||||
updateStackParent(@Auth() auth: AuthDto, @Body() dto: UpdateStackParentDto): Promise<void> {
|
||||
return this.service.updateStackParent(auth, dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
updateAsset(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: UpdateDto,
|
||||
): Promise<AssetResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
updateAsset(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateDto): Promise<AssetResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import {
|
|||
AuditDeletesDto,
|
||||
AuditDeletesResponseDto,
|
||||
AuditService,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
FileChecksumDto,
|
||||
FileChecksumResponseDto,
|
||||
FileReportDto,
|
||||
|
@ -10,7 +10,7 @@ import {
|
|||
} from '@app/domain';
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AdminRoute, AuthUser, Authenticated } from '../app.guard';
|
||||
import { AdminRoute, Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
|
||||
@ApiTags('Audit')
|
||||
|
@ -21,8 +21,8 @@ export class AuditController {
|
|||
constructor(private service: AuditService) {}
|
||||
|
||||
@Get('deletes')
|
||||
getAuditDeletes(@AuthUser() authUser: AuthUserDto, @Query() dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> {
|
||||
return this.service.getDeletes(authUser, dto);
|
||||
getAuditDeletes(@Auth() auth: AuthDto, @Query() dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> {
|
||||
return this.service.getDeletes(auth, dto);
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
AuthDeviceResponseDto,
|
||||
AuthDto,
|
||||
AuthService,
|
||||
AuthUserDto,
|
||||
ChangePasswordDto,
|
||||
IMMICH_ACCESS_COOKIE,
|
||||
IMMICH_AUTH_TYPE_COOKIE,
|
||||
|
@ -17,7 +17,7 @@ import {
|
|||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Request, Response } from 'express';
|
||||
import { AuthUser, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard';
|
||||
import { Auth, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -47,20 +47,20 @@ export class AuthController {
|
|||
}
|
||||
|
||||
@Get('devices')
|
||||
getAuthDevices(@AuthUser() authUser: AuthUserDto): Promise<AuthDeviceResponseDto[]> {
|
||||
return this.service.getDevices(authUser);
|
||||
getAuthDevices(@Auth() auth: AuthDto): Promise<AuthDeviceResponseDto[]> {
|
||||
return this.service.getDevices(auth);
|
||||
}
|
||||
|
||||
@Delete('devices')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
logoutAuthDevices(@AuthUser() authUser: AuthUserDto): Promise<void> {
|
||||
return this.service.logoutDevices(authUser);
|
||||
logoutAuthDevices(@Auth() auth: AuthDto): Promise<void> {
|
||||
return this.service.logoutDevices(auth);
|
||||
}
|
||||
|
||||
@Delete('devices/:id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
logoutAuthDevice(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.logoutDevice(authUser, id);
|
||||
logoutAuthDevice(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.logoutDevice(auth, id);
|
||||
}
|
||||
|
||||
@Post('validateToken')
|
||||
|
@ -71,8 +71,8 @@ export class AuthController {
|
|||
|
||||
@Post('change-password')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
changePassword(@AuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> {
|
||||
return this.service.changePassword(authUser, dto).then(mapUser);
|
||||
changePassword(@Auth() auth: AuthDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> {
|
||||
return this.service.changePassword(auth, dto).then(mapUser);
|
||||
}
|
||||
|
||||
@Post('logout')
|
||||
|
@ -80,11 +80,11 @@ export class AuthController {
|
|||
logout(
|
||||
@Req() req: Request,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
): Promise<LogoutResponseDto> {
|
||||
res.clearCookie(IMMICH_ACCESS_COOKIE);
|
||||
res.clearCookie(IMMICH_AUTH_TYPE_COOKIE);
|
||||
|
||||
return this.service.logout(authUser, (req.cookies || {})[IMMICH_AUTH_TYPE_COOKIE]);
|
||||
return this.service.logout(auth, (req.cookies || {})[IMMICH_AUTH_TYPE_COOKIE]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AssetFaceResponseDto, AuthUserDto, FaceDto, PersonResponseDto, PersonService } from '@app/domain';
|
||||
import { AssetFaceResponseDto, AuthDto, FaceDto, PersonResponseDto, PersonService } from '@app/domain';
|
||||
import { Body, Controller, Get, Param, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUser, Authenticated } from '../app.guard';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -13,16 +13,16 @@ export class FaceController {
|
|||
constructor(private service: PersonService) {}
|
||||
|
||||
@Get()
|
||||
getFaces(@AuthUser() authUser: AuthUserDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
||||
return this.service.getFacesById(authUser, dto);
|
||||
getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
||||
return this.service.getFacesById(auth, dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
reassignFacesById(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: FaceDto,
|
||||
): Promise<PersonResponseDto> {
|
||||
return this.service.reassignFacesById(authUser, id, dto);
|
||||
return this.service.reassignFacesById(auth, id, dto);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
CreateLibraryDto as CreateDto,
|
||||
LibraryService,
|
||||
LibraryStatsResponseDto,
|
||||
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@app/domain';
|
||||
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUser, Authenticated } from '../app.guard';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -21,49 +21,42 @@ export class LibraryController {
|
|||
constructor(private service: LibraryService) {}
|
||||
|
||||
@Get()
|
||||
getLibraries(@AuthUser() authUser: AuthUserDto): Promise<ResponseDto[]> {
|
||||
return this.service.getAllForUser(authUser);
|
||||
getLibraries(@Auth() auth: AuthDto): Promise<ResponseDto[]> {
|
||||
return this.service.getAllForUser(auth);
|
||||
}
|
||||
|
||||
@Post()
|
||||
createLibrary(@AuthUser() authUser: AuthUserDto, @Body() dto: CreateDto): Promise<ResponseDto> {
|
||||
return this.service.create(authUser, dto);
|
||||
createLibrary(@Auth() auth: AuthDto, @Body() dto: CreateDto): Promise<ResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
updateLibrary(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: UpdateDto,
|
||||
): Promise<ResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
updateLibrary(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateDto): Promise<ResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getLibraryInfo(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<ResponseDto> {
|
||||
return this.service.get(authUser, id);
|
||||
getLibraryInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<ResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
deleteLibrary(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(authUser, id);
|
||||
deleteLibrary(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@Get(':id/statistics')
|
||||
getLibraryStatistics(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
): Promise<LibraryStatsResponseDto> {
|
||||
return this.service.getStatistics(authUser, id);
|
||||
getLibraryStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
|
||||
return this.service.getStatistics(auth, id);
|
||||
}
|
||||
|
||||
@Post(':id/scan')
|
||||
scanLibrary(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, @Body() dto: ScanLibraryDto) {
|
||||
return this.service.queueScan(authUser, id, dto);
|
||||
scanLibrary(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: ScanLibraryDto) {
|
||||
return this.service.queueScan(auth, id, dto);
|
||||
}
|
||||
|
||||
@Post(':id/removeOffline')
|
||||
removeOfflineFiles(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.queueRemoveOffline(authUser, id);
|
||||
removeOfflineFiles(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.queueRemoveOffline(auth, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
AuthDto,
|
||||
AuthService,
|
||||
AuthUserDto,
|
||||
LoginDetails,
|
||||
LoginResponseDto,
|
||||
OAuthAuthorizeResponseDto,
|
||||
|
@ -12,7 +12,7 @@ import {
|
|||
import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Request, Response } from 'express';
|
||||
import { AuthUser, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard';
|
||||
import { Auth, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
|
||||
@ApiTags('OAuth')
|
||||
|
@ -58,12 +58,12 @@ export class OAuthController {
|
|||
}
|
||||
|
||||
@Post('link')
|
||||
linkOAuthAccount(@AuthUser() authUser: AuthUserDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> {
|
||||
return this.service.link(authUser, dto);
|
||||
linkOAuthAccount(@Auth() auth: AuthDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> {
|
||||
return this.service.link(auth, dto);
|
||||
}
|
||||
|
||||
@Post('unlink')
|
||||
unlinkOAuthAccount(@AuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
|
||||
return this.service.unlink(authUser);
|
||||
unlinkOAuthAccount(@Auth() auth: AuthDto): Promise<UserResponseDto> {
|
||||
return this.service.unlink(auth);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { AuthUserDto, PartnerDirection, PartnerService } from '@app/domain';
|
||||
import { AuthDto, PartnerDirection, PartnerService } from '@app/domain';
|
||||
import { PartnerResponseDto, UpdatePartnerDto } from '@app/domain/partner/partner.dto';
|
||||
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUser, Authenticated } from '../app.guard';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -15,29 +15,26 @@ export class PartnerController {
|
|||
|
||||
@Get()
|
||||
@ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true })
|
||||
getPartners(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Query('direction') direction: PartnerDirection,
|
||||
): Promise<PartnerResponseDto[]> {
|
||||
return this.service.getAll(authUser, direction);
|
||||
getPartners(@Auth() auth: AuthDto, @Query('direction') direction: PartnerDirection): Promise<PartnerResponseDto[]> {
|
||||
return this.service.getAll(auth, direction);
|
||||
}
|
||||
|
||||
@Post(':id')
|
||||
createPartner(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
|
||||
return this.service.create(authUser, id);
|
||||
createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
|
||||
return this.service.create(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
updatePartner(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: UpdatePartnerDto,
|
||||
): Promise<PartnerResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
removePartner(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(authUser, id);
|
||||
removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(auth, id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
AssetFaceUpdateDto,
|
||||
AssetResponseDto,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
BulkIdResponseDto,
|
||||
ImmichReadStream,
|
||||
MergePersonDto,
|
||||
|
@ -15,7 +15,7 @@ import {
|
|||
} from '@app/domain';
|
||||
import { Body, Controller, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUser, Authenticated } from '../app.guard';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -31,49 +31,46 @@ export class PersonController {
|
|||
constructor(private service: PersonService) {}
|
||||
|
||||
@Get()
|
||||
getAllPeople(@AuthUser() authUser: AuthUserDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> {
|
||||
return this.service.getAll(authUser, withHidden);
|
||||
getAllPeople(@Auth() auth: AuthDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> {
|
||||
return this.service.getAll(auth, withHidden);
|
||||
}
|
||||
|
||||
@Post()
|
||||
createPerson(@AuthUser() authUser: AuthUserDto): Promise<PersonResponseDto> {
|
||||
return this.service.createPerson(authUser);
|
||||
createPerson(@Auth() auth: AuthDto): Promise<PersonResponseDto> {
|
||||
return this.service.createPerson(auth);
|
||||
}
|
||||
|
||||
@Put(':id/reassign')
|
||||
reassignFaces(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AssetFaceUpdateDto,
|
||||
): Promise<PersonResponseDto[]> {
|
||||
return this.service.reassignFaces(authUser, id, dto);
|
||||
return this.service.reassignFaces(auth, id, dto);
|
||||
}
|
||||
|
||||
@Put()
|
||||
updatePeople(@AuthUser() authUser: AuthUserDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||
return this.service.updatePeople(authUser, dto);
|
||||
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||
return this.service.updatePeople(auth, dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getPerson(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
|
||||
return this.service.getById(authUser, id);
|
||||
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
|
||||
return this.service.getById(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
updatePerson(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: PersonUpdateDto,
|
||||
): Promise<PersonResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Get(':id/statistics')
|
||||
getPersonStatistics(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
): Promise<PersonStatisticsResponseDto> {
|
||||
return this.service.getStatistics(authUser, id);
|
||||
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
|
||||
return this.service.getStatistics(auth, id);
|
||||
}
|
||||
|
||||
@Get(':id/thumbnail')
|
||||
|
@ -82,21 +79,21 @@ export class PersonController {
|
|||
'image/jpeg': { schema: { type: 'string', format: 'binary' } },
|
||||
},
|
||||
})
|
||||
getPersonThumbnail(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.getThumbnail(authUser, id).then(asStreamableFile);
|
||||
getPersonThumbnail(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.getThumbnail(auth, id).then(asStreamableFile);
|
||||
}
|
||||
|
||||
@Get(':id/assets')
|
||||
getPersonAssets(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getAssets(authUser, id);
|
||||
getPersonAssets(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getAssets(auth, id);
|
||||
}
|
||||
|
||||
@Post(':id/merge')
|
||||
mergePerson(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: MergePersonDto,
|
||||
): Promise<BulkIdResponseDto[]> {
|
||||
return this.service.mergePerson(authUser, id, dto);
|
||||
return this.service.mergePerson(auth, id, dto);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
PersonResponseDto,
|
||||
SearchDto,
|
||||
SearchExploreResponseDto,
|
||||
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@app/domain';
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUser, Authenticated } from '../app.guard';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
|
||||
@ApiTags('Search')
|
||||
|
@ -20,17 +20,17 @@ export class SearchController {
|
|||
constructor(private service: SearchService) {}
|
||||
|
||||
@Get()
|
||||
search(@AuthUser() authUser: AuthUserDto, @Query() dto: SearchDto): Promise<SearchResponseDto> {
|
||||
return this.service.search(authUser, dto);
|
||||
search(@Auth() auth: AuthDto, @Query() dto: SearchDto): Promise<SearchResponseDto> {
|
||||
return this.service.search(auth, dto);
|
||||
}
|
||||
|
||||
@Get('explore')
|
||||
getExploreData(@AuthUser() authUser: AuthUserDto): Promise<SearchExploreResponseDto[]> {
|
||||
return this.service.getExploreData(authUser) as Promise<SearchExploreResponseDto[]>;
|
||||
getExploreData(@Auth() auth: AuthDto): Promise<SearchExploreResponseDto[]> {
|
||||
return this.service.getExploreData(auth) as Promise<SearchExploreResponseDto[]>;
|
||||
}
|
||||
|
||||
@Get('person')
|
||||
searchPerson(@AuthUser() authUser: AuthUserDto, @Query() dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
||||
return this.service.searchPerson(authUser, dto);
|
||||
searchPerson(@Auth() auth: AuthDto, @Query() dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
||||
return this.service.searchPerson(auth, dto);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
AssetIdsDto,
|
||||
AssetIdsResponseDto,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
IMMICH_SHARED_LINK_ACCESS_COOKIE,
|
||||
SharedLinkCreateDto,
|
||||
SharedLinkEditDto,
|
||||
|
@ -12,7 +12,7 @@ import {
|
|||
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req, Res } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Request, Response } from 'express';
|
||||
import { AuthUser, Authenticated, SharedLinkRoute } from '../app.guard';
|
||||
import { Auth, Authenticated, SharedLinkRoute } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -24,14 +24,14 @@ export class SharedLinkController {
|
|||
constructor(private readonly service: SharedLinkService) {}
|
||||
|
||||
@Get()
|
||||
getAllSharedLinks(@AuthUser() authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.service.getAll(authUser);
|
||||
getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.service.getAll(auth);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Get('me')
|
||||
async getMySharedLink(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Query() dto: SharedLinkPasswordDto,
|
||||
@Req() req: Request,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
|
@ -40,58 +40,58 @@ export class SharedLinkController {
|
|||
if (sharedLinkToken) {
|
||||
dto.token = sharedLinkToken;
|
||||
}
|
||||
const sharedLinkResponse = await this.service.getMine(authUser, dto);
|
||||
if (sharedLinkResponse.token) {
|
||||
res.cookie(IMMICH_SHARED_LINK_ACCESS_COOKIE, sharedLinkResponse.token, {
|
||||
const response = await this.service.getMine(auth, dto);
|
||||
if (response.token) {
|
||||
res.cookie(IMMICH_SHARED_LINK_ACCESS_COOKIE, response.token, {
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24),
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
});
|
||||
}
|
||||
return sharedLinkResponse;
|
||||
return response;
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getSharedLinkById(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
|
||||
return this.service.get(authUser, id);
|
||||
getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
createSharedLink(@AuthUser() authUser: AuthUserDto, @Body() dto: SharedLinkCreateDto) {
|
||||
return this.service.create(authUser, dto);
|
||||
createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
updateSharedLink(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: SharedLinkEditDto,
|
||||
): Promise<SharedLinkResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
removeSharedLink(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(authUser, id);
|
||||
removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(auth, id);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Put(':id/assets')
|
||||
addSharedLinkAssets(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AssetIdsDto,
|
||||
): Promise<AssetIdsResponseDto[]> {
|
||||
return this.service.addAssets(authUser, id, dto);
|
||||
return this.service.addAssets(auth, id, dto);
|
||||
}
|
||||
|
||||
@SharedLinkRoute()
|
||||
@Delete(':id/assets')
|
||||
removeSharedLinkAssets(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AssetIdsDto,
|
||||
): Promise<AssetIdsResponseDto[]> {
|
||||
return this.service.removeAssets(authUser, id, dto);
|
||||
return this.service.removeAssets(auth, id, dto);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import {
|
|||
AssetIdsDto,
|
||||
AssetIdsResponseDto,
|
||||
AssetResponseDto,
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
CreateTagDto,
|
||||
TagResponseDto,
|
||||
TagService,
|
||||
|
@ -10,7 +10,7 @@ import {
|
|||
} from '@app/domain';
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthUser, Authenticated } from '../app.guard';
|
||||
import { Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation } from '../app.utils';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
||||
|
@ -22,54 +22,50 @@ export class TagController {
|
|||
constructor(private service: TagService) {}
|
||||
|
||||
@Post()
|
||||
createTag(@AuthUser() authUser: AuthUserDto, @Body() dto: CreateTagDto): Promise<TagResponseDto> {
|
||||
return this.service.create(authUser, dto);
|
||||
createTag(@Auth() auth: AuthDto, @Body() dto: CreateTagDto): Promise<TagResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
getAllTags(@AuthUser() authUser: AuthUserDto): Promise<TagResponseDto[]> {
|
||||
return this.service.getAll(authUser);
|
||||
getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
|
||||
return this.service.getAll(auth);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getTagById(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
|
||||
return this.service.getById(authUser, id);
|
||||
getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
|
||||
return this.service.getById(auth, id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
updateTag(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: UpdateTagDto,
|
||||
): Promise<TagResponseDto> {
|
||||
return this.service.update(authUser, id, dto);
|
||||
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateTagDto): Promise<TagResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
deleteTag(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(authUser, id);
|
||||
deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(auth, id);
|
||||
}
|
||||
|
||||
@Get(':id/assets')
|
||||
getTagAssets(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getAssets(authUser, id);
|
||||
getTagAssets(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getAssets(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id/assets')
|
||||
tagAssets(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: AssetIdsDto,
|
||||
): Promise<AssetIdsResponseDto[]> {
|
||||
return this.service.addAssets(authUser, id, dto);
|
||||
return this.service.addAssets(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id/assets')
|
||||
untagAssets(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@Body() dto: AssetIdsDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
): Promise<AssetIdsResponseDto[]> {
|
||||
return this.service.removeAssets(authUser, id, dto);
|
||||
return this.service.removeAssets(auth, id, dto);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
AuthUserDto,
|
||||
AuthDto,
|
||||
CreateUserDto as CreateDto,
|
||||
CreateProfileImageDto,
|
||||
CreateProfileImageResponseDto,
|
||||
|
@ -23,7 +23,7 @@ import {
|
|||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminRoute, AuthUser, Authenticated } from '../app.guard';
|
||||
import { AdminRoute, Auth, Authenticated } from '../app.guard';
|
||||
import { UseValidation, asStreamableFile } from '../app.utils';
|
||||
import { FileUploadInterceptor, Route } from '../interceptors';
|
||||
import { UUIDParamDto } from './dto/uuid-param.dto';
|
||||
|
@ -36,8 +36,8 @@ export class UserController {
|
|||
constructor(private service: UserService) {}
|
||||
|
||||
@Get()
|
||||
getAllUsers(@AuthUser() authUser: AuthUserDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> {
|
||||
return this.service.getAll(authUser, isAll);
|
||||
getAllUsers(@Auth() auth: AuthDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> {
|
||||
return this.service.getAll(auth, isAll);
|
||||
}
|
||||
|
||||
@Get('info/:id')
|
||||
|
@ -46,8 +46,8 @@ export class UserController {
|
|||
}
|
||||
|
||||
@Get('me')
|
||||
getMyUserInfo(@AuthUser() authUser: AuthUserDto): Promise<UserResponseDto> {
|
||||
return this.service.getMe(authUser);
|
||||
getMyUserInfo(@Auth() auth: AuthDto): Promise<UserResponseDto> {
|
||||
return this.service.getMe(auth);
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
|
@ -58,26 +58,26 @@ export class UserController {
|
|||
|
||||
@Delete('profile-image')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteProfileImage(@AuthUser() authUser: AuthUserDto): Promise<void> {
|
||||
return this.service.deleteProfileImage(authUser);
|
||||
deleteProfileImage(@Auth() auth: AuthDto): Promise<void> {
|
||||
return this.service.deleteProfileImage(auth);
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@Delete(':id')
|
||||
deleteUser(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
|
||||
return this.service.delete(authUser, id);
|
||||
deleteUser(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@AdminRoute()
|
||||
@Post(':id/restore')
|
||||
restoreUser(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
|
||||
return this.service.restore(authUser, id);
|
||||
restoreUser(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
|
||||
return this.service.restore(auth, id);
|
||||
}
|
||||
|
||||
// TODO: replace with @Put(':id')
|
||||
@Put()
|
||||
updateUser(@AuthUser() authUser: AuthUserDto, @Body() updateUserDto: UpdateDto): Promise<UserResponseDto> {
|
||||
return this.service.update(authUser, updateUserDto);
|
||||
updateUser(@Auth() auth: AuthDto, @Body() updateUserDto: UpdateDto): Promise<UserResponseDto> {
|
||||
return this.service.update(auth, updateUserDto);
|
||||
}
|
||||
|
||||
@UseInterceptors(FileUploadInterceptor)
|
||||
|
@ -85,10 +85,10 @@ export class UserController {
|
|||
@ApiBody({ description: 'A new avatar for the user', type: CreateProfileImageDto })
|
||||
@Post('profile-image')
|
||||
createProfileImage(
|
||||
@AuthUser() authUser: AuthUserDto,
|
||||
@Auth() auth: AuthDto,
|
||||
@UploadedFile() fileInfo: Express.Multer.File,
|
||||
): Promise<CreateProfileImageResponseDto> {
|
||||
return this.service.createProfileImage(authUser, fileInfo);
|
||||
return this.service.createProfileImage(auth, fileInfo);
|
||||
}
|
||||
|
||||
@Get('profile-image/:id')
|
||||
|
|
|
@ -44,7 +44,7 @@ const callbackify = async <T>(fn: (...args: any[]) => T, callback: Callback<T>)
|
|||
|
||||
const asRequest = (req: AuthRequest, file: Express.Multer.File) => {
|
||||
return {
|
||||
authUser: req.user || null,
|
||||
auth: req.user || null,
|
||||
fieldName: file.fieldname as UploadFieldName,
|
||||
file: mapToUploadFile(file as ImmichFile),
|
||||
};
|
||||
|
|
|
@ -19,10 +19,10 @@ export class CommunicationRepository implements OnGatewayConnection, OnGatewayDi
|
|||
async handleConnection(client: Socket) {
|
||||
try {
|
||||
this.logger.log(`Websocket Connect: ${client.id}`);
|
||||
const user = await this.authService.validate(client.request.headers, {});
|
||||
await client.join(user.id);
|
||||
const auth = await this.authService.validate(client.request.headers, {});
|
||||
await client.join(auth.user.id);
|
||||
for (const callback of this.onConnectCallbacks) {
|
||||
await callback(user.id);
|
||||
await callback(auth.user.id);
|
||||
}
|
||||
} catch (error: Error | any) {
|
||||
this.logger.error(`Websocket connection error: ${error}`, error?.stack);
|
||||
|
|
4
server/test/fixtures/activity.stub.ts
vendored
4
server/test/fixtures/activity.stub.ts
vendored
|
@ -9,7 +9,7 @@ export const activityStub = {
|
|||
id: 'activity-1',
|
||||
comment: 'comment',
|
||||
isLiked: false,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
assetId: assetStub.image.id,
|
||||
asset: assetStub.image,
|
||||
|
@ -22,7 +22,7 @@ export const activityStub = {
|
|||
id: 'activity-2',
|
||||
comment: null,
|
||||
isLiked: true,
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
assetId: assetStub.image.id,
|
||||
asset: assetStub.image,
|
||||
|
|
20
server/test/fixtures/album.stub.ts
vendored
20
server/test/fixtures/album.stub.ts
vendored
|
@ -8,7 +8,7 @@ export const albumStub = {
|
|||
id: 'album-1',
|
||||
albumName: 'Empty album',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
|
@ -24,7 +24,7 @@ export const albumStub = {
|
|||
id: 'album-2',
|
||||
albumName: 'Empty album shared with user',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
|
@ -40,7 +40,7 @@ export const albumStub = {
|
|||
id: 'album-3',
|
||||
albumName: 'Empty album shared with users',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
|
@ -56,7 +56,7 @@ export const albumStub = {
|
|||
id: 'album-3',
|
||||
albumName: 'Empty album shared with admin',
|
||||
description: '',
|
||||
ownerId: authStub.user1.id,
|
||||
ownerId: authStub.user1.user.id,
|
||||
owner: userStub.user1,
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
|
@ -72,7 +72,7 @@ export const albumStub = {
|
|||
id: 'album-4',
|
||||
albumName: 'Album with one asset',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [assetStub.image],
|
||||
albumThumbnailAsset: null,
|
||||
|
@ -88,7 +88,7 @@ export const albumStub = {
|
|||
id: 'album-4a',
|
||||
albumName: 'Album with two assets',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [assetStub.image, assetStub.withLocation],
|
||||
albumThumbnailAsset: assetStub.image,
|
||||
|
@ -104,7 +104,7 @@ export const albumStub = {
|
|||
id: 'album-5',
|
||||
albumName: 'Empty album with invalid thumbnail',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [],
|
||||
albumThumbnailAsset: assetStub.image,
|
||||
|
@ -120,7 +120,7 @@ export const albumStub = {
|
|||
id: 'album-5',
|
||||
albumName: 'Empty album with invalid thumbnail',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [],
|
||||
albumThumbnailAsset: null,
|
||||
|
@ -136,7 +136,7 @@ export const albumStub = {
|
|||
id: 'album-6',
|
||||
albumName: 'Album with one asset and invalid thumbnail',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [assetStub.image],
|
||||
albumThumbnailAsset: assetStub.livePhotoMotionAsset,
|
||||
|
@ -152,7 +152,7 @@ export const albumStub = {
|
|||
id: 'album-6',
|
||||
albumName: 'Album with one asset and invalid thumbnail',
|
||||
description: '',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
assets: [assetStub.image],
|
||||
albumThumbnailAsset: assetStub.image,
|
||||
|
|
2
server/test/fixtures/api-key.stub.ts
vendored
2
server/test/fixtures/api-key.stub.ts
vendored
|
@ -7,7 +7,7 @@ export const keyStub = {
|
|||
id: 'my-random-guid',
|
||||
name: 'My Key',
|
||||
key: 'my-api-key (hashed)',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
} as APIKeyEntity),
|
||||
};
|
||||
|
|
4
server/test/fixtures/asset.stub.ts
vendored
4
server/test/fixtures/asset.stub.ts
vendored
|
@ -403,7 +403,7 @@ export const assetStub = {
|
|||
livePhotoMotionAsset: Object.freeze({
|
||||
id: 'live-photo-motion-asset',
|
||||
originalPath: fileStub.livePhotoMotion.originalPath,
|
||||
ownerId: authStub.user1.id,
|
||||
ownerId: authStub.user1.user.id,
|
||||
type: AssetType.VIDEO,
|
||||
isVisible: false,
|
||||
fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'),
|
||||
|
@ -418,7 +418,7 @@ export const assetStub = {
|
|||
livePhotoStillAsset: Object.freeze({
|
||||
id: 'live-photo-still-asset',
|
||||
originalPath: fileStub.livePhotoStill.originalPath,
|
||||
ownerId: authStub.user1.id,
|
||||
ownerId: authStub.user1.user.id,
|
||||
type: AssetType.IMAGE,
|
||||
livePhotoVideoId: 'live-photo-motion-asset',
|
||||
isVisible: true,
|
||||
|
|
6
server/test/fixtures/audit.stub.ts
vendored
6
server/test/fixtures/audit.stub.ts
vendored
|
@ -7,7 +7,7 @@ export const auditStub = {
|
|||
entityId: 'asset-created',
|
||||
action: DatabaseAction.CREATE,
|
||||
entityType: EntityType.ASSET,
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
createdAt: new Date(),
|
||||
}),
|
||||
update: Object.freeze<AuditEntity>({
|
||||
|
@ -15,7 +15,7 @@ export const auditStub = {
|
|||
entityId: 'asset-updated',
|
||||
action: DatabaseAction.UPDATE,
|
||||
entityType: EntityType.ASSET,
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
createdAt: new Date(),
|
||||
}),
|
||||
delete: Object.freeze<AuditEntity>({
|
||||
|
@ -23,7 +23,7 @@ export const auditStub = {
|
|||
entityId: 'asset-deleted',
|
||||
action: DatabaseAction.DELETE,
|
||||
entityType: EntityType.ASSET,
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
createdAt: new Date(),
|
||||
}),
|
||||
};
|
||||
|
|
140
server/test/fixtures/auth.stub.ts
vendored
140
server/test/fixtures/auth.stub.ts
vendored
|
@ -1,4 +1,5 @@
|
|||
import { AuthUserDto } from '@app/domain';
|
||||
import { AuthDto } from '@app/domain';
|
||||
import { SharedLinkEntity, UserEntity, UserTokenEntity } from '../../src/infra/entities';
|
||||
|
||||
export const adminSignupStub = {
|
||||
name: 'Immich Admin',
|
||||
|
@ -24,77 +25,84 @@ export const changePasswordStub = {
|
|||
};
|
||||
|
||||
export const authStub = {
|
||||
admin: Object.freeze<AuthUserDto>({
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
externalPath: null,
|
||||
admin: Object.freeze<AuthDto>({
|
||||
user: {
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
} as UserEntity,
|
||||
}),
|
||||
user1: Object.freeze<AuthUserDto>({
|
||||
id: 'user-id',
|
||||
email: 'immich@test.com',
|
||||
isAdmin: false,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
isAllowDownload: true,
|
||||
isShowMetadata: true,
|
||||
accessTokenId: 'token-id',
|
||||
externalPath: null,
|
||||
user1: Object.freeze<AuthDto>({
|
||||
user: {
|
||||
id: 'user-id',
|
||||
email: 'immich@test.com',
|
||||
isAdmin: false,
|
||||
} as UserEntity,
|
||||
userToken: {
|
||||
id: 'token-id',
|
||||
} as UserTokenEntity,
|
||||
}),
|
||||
user2: Object.freeze<AuthUserDto>({
|
||||
id: 'user-2',
|
||||
email: 'user2@immich.app',
|
||||
isAdmin: false,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
isAllowDownload: true,
|
||||
isShowMetadata: true,
|
||||
accessTokenId: 'token-id',
|
||||
externalPath: null,
|
||||
user2: Object.freeze<AuthDto>({
|
||||
user: {
|
||||
id: 'user-2',
|
||||
email: 'user2@immich.app',
|
||||
isAdmin: false,
|
||||
} as UserEntity,
|
||||
userToken: {
|
||||
id: 'token-id',
|
||||
} as UserTokenEntity,
|
||||
}),
|
||||
external1: Object.freeze<AuthUserDto>({
|
||||
id: 'user-id',
|
||||
email: 'immich@test.com',
|
||||
isAdmin: false,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
isAllowDownload: true,
|
||||
isShowMetadata: true,
|
||||
accessTokenId: 'token-id',
|
||||
externalPath: '/data/user1',
|
||||
external1: Object.freeze<AuthDto>({
|
||||
user: {
|
||||
id: 'user-id',
|
||||
email: 'immich@test.com',
|
||||
isAdmin: false,
|
||||
externalPath: '/data/user1',
|
||||
} as UserEntity,
|
||||
userToken: {
|
||||
id: 'token-id',
|
||||
} as UserTokenEntity,
|
||||
}),
|
||||
adminSharedLink: Object.freeze<AuthUserDto>({
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
isAllowUpload: true,
|
||||
isAllowDownload: true,
|
||||
isPublicUser: true,
|
||||
isShowMetadata: true,
|
||||
sharedLinkId: '123',
|
||||
adminSharedLink: Object.freeze<AuthDto>({
|
||||
user: {
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
} as UserEntity,
|
||||
sharedLink: {
|
||||
id: '123',
|
||||
showExif: true,
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
key: Buffer.from('shared-link-key'),
|
||||
} as SharedLinkEntity,
|
||||
}),
|
||||
adminSharedLinkNoExif: Object.freeze<AuthUserDto>({
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
isAllowUpload: true,
|
||||
isAllowDownload: true,
|
||||
isPublicUser: true,
|
||||
isShowMetadata: false,
|
||||
sharedLinkId: '123',
|
||||
adminSharedLinkNoExif: Object.freeze<AuthDto>({
|
||||
user: {
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
} as UserEntity,
|
||||
sharedLink: {
|
||||
id: '123',
|
||||
showExif: false,
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
key: Buffer.from('shared-link-key'),
|
||||
} as SharedLinkEntity,
|
||||
}),
|
||||
readonlySharedLink: Object.freeze<AuthUserDto>({
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
isAllowUpload: false,
|
||||
isAllowDownload: false,
|
||||
isPublicUser: true,
|
||||
isShowMetadata: true,
|
||||
sharedLinkId: '123',
|
||||
accessTokenId: 'token-id',
|
||||
readonlySharedLink: Object.freeze<AuthDto>({
|
||||
user: {
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
} as UserEntity,
|
||||
sharedLink: {
|
||||
id: '123',
|
||||
allowUpload: false,
|
||||
allowDownload: false,
|
||||
showExif: true,
|
||||
} as SharedLinkEntity,
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
12
server/test/fixtures/shared-link.stub.ts
vendored
12
server/test/fixtures/shared-link.stub.ts
vendored
|
@ -106,7 +106,7 @@ const albumResponse: AlbumResponseDto = {
|
|||
export const sharedLinkStub = {
|
||||
individual: Object.freeze({
|
||||
id: '123',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
key: sharedLinkBytes,
|
||||
type: SharedLinkType.INDIVIDUAL,
|
||||
|
@ -121,7 +121,7 @@ export const sharedLinkStub = {
|
|||
} as SharedLinkEntity),
|
||||
valid: Object.freeze({
|
||||
id: '123',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
key: sharedLinkBytes,
|
||||
type: SharedLinkType.ALBUM,
|
||||
|
@ -138,7 +138,7 @@ export const sharedLinkStub = {
|
|||
} as SharedLinkEntity),
|
||||
expired: Object.freeze({
|
||||
id: '123',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
key: sharedLinkBytes,
|
||||
type: SharedLinkType.ALBUM,
|
||||
|
@ -154,7 +154,7 @@ export const sharedLinkStub = {
|
|||
} as SharedLinkEntity),
|
||||
readonlyNoExif: Object.freeze<SharedLinkEntity>({
|
||||
id: '123',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
key: sharedLinkBytes,
|
||||
type: SharedLinkType.ALBUM,
|
||||
|
@ -169,7 +169,7 @@ export const sharedLinkStub = {
|
|||
albumId: 'album-123',
|
||||
album: {
|
||||
id: 'album-123',
|
||||
ownerId: authStub.admin.id,
|
||||
ownerId: authStub.admin.user.id,
|
||||
owner: userStub.admin,
|
||||
albumName: 'Test Album',
|
||||
description: '',
|
||||
|
@ -260,7 +260,7 @@ export const sharedLinkStub = {
|
|||
}),
|
||||
passwordRequired: Object.freeze<SharedLinkEntity>({
|
||||
id: '123',
|
||||
userId: authStub.admin.id,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
key: sharedLinkBytes,
|
||||
type: SharedLinkType.ALBUM,
|
||||
|
|
14
server/test/fixtures/user.stub.ts
vendored
14
server/test/fixtures/user.stub.ts
vendored
|
@ -21,7 +21,7 @@ export const userDto = {
|
|||
|
||||
export const userStub = {
|
||||
admin: Object.freeze<UserEntity>({
|
||||
...authStub.admin,
|
||||
...authStub.admin.user,
|
||||
password: 'admin_password',
|
||||
name: 'admin_name',
|
||||
storageLabel: 'admin',
|
||||
|
@ -38,7 +38,7 @@ export const userStub = {
|
|||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
}),
|
||||
user1: Object.freeze<UserEntity>({
|
||||
...authStub.user1,
|
||||
...authStub.user1.user,
|
||||
password: 'immich_password',
|
||||
name: 'immich_name',
|
||||
storageLabel: null,
|
||||
|
@ -55,7 +55,7 @@ export const userStub = {
|
|||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
}),
|
||||
user2: Object.freeze<UserEntity>({
|
||||
...authStub.user2,
|
||||
...authStub.user2.user,
|
||||
password: 'immich_password',
|
||||
name: 'immich_name',
|
||||
storageLabel: null,
|
||||
|
@ -72,7 +72,7 @@ export const userStub = {
|
|||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
}),
|
||||
storageLabel: Object.freeze<UserEntity>({
|
||||
...authStub.user1,
|
||||
...authStub.user1.user,
|
||||
password: 'immich_password',
|
||||
name: 'immich_name',
|
||||
storageLabel: 'label-1',
|
||||
|
@ -89,7 +89,7 @@ export const userStub = {
|
|||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
}),
|
||||
externalPath1: Object.freeze<UserEntity>({
|
||||
...authStub.user1,
|
||||
...authStub.user1.user,
|
||||
password: 'immich_password',
|
||||
name: 'immich_name',
|
||||
storageLabel: 'label-1',
|
||||
|
@ -106,7 +106,7 @@ export const userStub = {
|
|||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
}),
|
||||
externalPath2: Object.freeze<UserEntity>({
|
||||
...authStub.user1,
|
||||
...authStub.user1.user,
|
||||
password: 'immich_password',
|
||||
name: 'immich_name',
|
||||
storageLabel: 'label-1',
|
||||
|
@ -123,7 +123,7 @@ export const userStub = {
|
|||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
}),
|
||||
profilePath: Object.freeze<UserEntity>({
|
||||
...authStub.user1,
|
||||
...authStub.user1.user,
|
||||
password: 'immich_password',
|
||||
name: 'immich_name',
|
||||
storageLabel: 'label-1',
|
||||
|
|
Loading…
Add table
Reference in a new issue