mirror of
https://github.com/immich-app/immich.git
synced 2026-03-26 11:50:53 +03:00
fix: shared link add to album (#27063)
This commit is contained in:
@@ -24,7 +24,7 @@
|
|||||||
"typeorm": "typeorm",
|
"typeorm": "typeorm",
|
||||||
"migrations:debug": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations generate --debug",
|
"migrations:debug": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations generate --debug",
|
||||||
"migrations:generate": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations generate",
|
"migrations:generate": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations generate",
|
||||||
"migrations:create": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations generate",
|
"migrations:create": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations create",
|
||||||
"migrations:run": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations run",
|
"migrations:run": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations run",
|
||||||
"migrations:revert": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations revert",
|
"migrations:revert": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} migrations revert",
|
||||||
"schema:drop": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} query 'DROP schema public cascade; CREATE schema public;'",
|
"schema:drop": "sql-tools -u ${DB_URL:-postgres://postgres:postgres@localhost:5432/immich} query 'DROP schema public cascade; CREATE schema public;'",
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ export type AuthSharedLink = {
|
|||||||
id: string;
|
id: string;
|
||||||
expiresAt: Date | null;
|
expiresAt: Date | null;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
albumId: string | null;
|
||||||
showExif: boolean;
|
showExif: boolean;
|
||||||
allowUpload: boolean;
|
allowUpload: boolean;
|
||||||
allowDownload: boolean;
|
allowDownload: boolean;
|
||||||
@@ -357,15 +358,6 @@ export const columns = {
|
|||||||
authUser: ['user.id', 'user.name', 'user.email', 'user.isAdmin', 'user.quotaUsageInBytes', 'user.quotaSizeInBytes'],
|
authUser: ['user.id', 'user.name', 'user.email', 'user.isAdmin', 'user.quotaUsageInBytes', 'user.quotaSizeInBytes'],
|
||||||
authApiKey: ['api_key.id', 'api_key.permissions'],
|
authApiKey: ['api_key.id', 'api_key.permissions'],
|
||||||
authSession: ['session.id', 'session.updatedAt', 'session.pinExpiresAt', 'session.appVersion'],
|
authSession: ['session.id', 'session.updatedAt', 'session.pinExpiresAt', 'session.appVersion'],
|
||||||
authSharedLink: [
|
|
||||||
'shared_link.id',
|
|
||||||
'shared_link.userId',
|
|
||||||
'shared_link.expiresAt',
|
|
||||||
'shared_link.showExif',
|
|
||||||
'shared_link.allowUpload',
|
|
||||||
'shared_link.allowDownload',
|
|
||||||
'shared_link.password',
|
|
||||||
],
|
|
||||||
user: userColumns,
|
user: userColumns,
|
||||||
userWithPrefix: userWithPrefixColumns,
|
userWithPrefix: userWithPrefixColumns,
|
||||||
userAdmin: [
|
userAdmin: [
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ order by
|
|||||||
select
|
select
|
||||||
"shared_link"."id",
|
"shared_link"."id",
|
||||||
"shared_link"."userId",
|
"shared_link"."userId",
|
||||||
|
"shared_link"."albumId",
|
||||||
"shared_link"."expiresAt",
|
"shared_link"."expiresAt",
|
||||||
"shared_link"."showExif",
|
"shared_link"."showExif",
|
||||||
"shared_link"."allowUpload",
|
"shared_link"."allowUpload",
|
||||||
@@ -211,6 +212,7 @@ where
|
|||||||
select
|
select
|
||||||
"shared_link"."id",
|
"shared_link"."id",
|
||||||
"shared_link"."userId",
|
"shared_link"."userId",
|
||||||
|
"shared_link"."albumId",
|
||||||
"shared_link"."expiresAt",
|
"shared_link"."expiresAt",
|
||||||
"shared_link"."showExif",
|
"shared_link"."showExif",
|
||||||
"shared_link"."allowUpload",
|
"shared_link"."allowUpload",
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ export class AlbumRepository {
|
|||||||
await db
|
await db
|
||||||
.insertInto('album_asset')
|
.insertInto('album_asset')
|
||||||
.values(assetIds.map((assetId) => ({ albumId, assetId })))
|
.values(assetIds.map((assetId) => ({ albumId, assetId })))
|
||||||
|
.onConflict((oc) => oc.doNothing())
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,14 @@ export class SharedLinkRepository {
|
|||||||
.leftJoin('album', 'album.id', 'shared_link.albumId')
|
.leftJoin('album', 'album.id', 'shared_link.albumId')
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.select((eb) => [
|
.select((eb) => [
|
||||||
...columns.authSharedLink,
|
'shared_link.id',
|
||||||
|
'shared_link.userId',
|
||||||
|
'shared_link.albumId',
|
||||||
|
'shared_link.expiresAt',
|
||||||
|
'shared_link.showExif',
|
||||||
|
'shared_link.allowUpload',
|
||||||
|
'shared_link.allowDownload',
|
||||||
|
'shared_link.password',
|
||||||
jsonObjectFrom(
|
jsonObjectFrom(
|
||||||
eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'),
|
eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'),
|
||||||
).as('user'),
|
).as('user'),
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`
|
||||||
|
DELETE FROM "shared_link_asset"
|
||||||
|
USING "shared_link"
|
||||||
|
WHERE "shared_link_asset"."sharedLinkId" = "shared_link"."id" AND "shared_link"."type" = 'ALBUM';
|
||||||
|
`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(): Promise<void> {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
@@ -165,6 +165,12 @@ export class AlbumService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||||
|
if (auth.sharedLink) {
|
||||||
|
this.logger.deprecate(
|
||||||
|
'Assets uploaded to a shared link are automatically added and calling this endpoint is no longer necessary. It will be removed in the next major release.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const album = await this.findOrFail(id, { withAssets: false });
|
const album = await this.findOrFail(id, { withAssets: false });
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumAssetCreate, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumAssetCreate, ids: [id] });
|
||||||
|
|
||||||
@@ -195,6 +201,12 @@ export class AlbumService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addAssetsToAlbums(auth: AuthDto, dto: AlbumsAddAssetsDto): Promise<AlbumsAddAssetsResponseDto> {
|
async addAssetsToAlbums(auth: AuthDto, dto: AlbumsAddAssetsDto): Promise<AlbumsAddAssetsResponseDto> {
|
||||||
|
if (auth.sharedLink) {
|
||||||
|
this.logger.deprecate(
|
||||||
|
'Assets uploaded to a shared link are automatically added and calling this endpoint is no longer necessary. It will be removed in the next major release.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const results: AlbumsAddAssetsResponseDto = {
|
const results: AlbumsAddAssetsResponseDto = {
|
||||||
success: false,
|
success: false,
|
||||||
error: BulkIdErrorReason.DUPLICATE,
|
error: BulkIdErrorReason.DUPLICATE,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { BadRequestException, Injectable, InternalServerErrorException, NotFound
|
|||||||
import { extname } from 'node:path';
|
import { extname } from 'node:path';
|
||||||
import sanitize from 'sanitize-filename';
|
import sanitize from 'sanitize-filename';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { Asset } from 'src/database';
|
import { Asset, AuthSharedLink } from 'src/database';
|
||||||
import {
|
import {
|
||||||
AssetBulkUploadCheckResponseDto,
|
AssetBulkUploadCheckResponseDto,
|
||||||
AssetMediaResponseDto,
|
AssetMediaResponseDto,
|
||||||
@@ -152,7 +152,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
const asset = await this.create(auth.user.id, dto, file, sidecarFile);
|
const asset = await this.create(auth.user.id, dto, file, sidecarFile);
|
||||||
|
|
||||||
if (auth.sharedLink) {
|
if (auth.sharedLink) {
|
||||||
await this.sharedLinkRepository.addAssets(auth.sharedLink.id, [asset.id]);
|
await this.addToSharedLink(auth.sharedLink, asset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.userRepository.updateUsage(auth.user.id, file.size);
|
await this.userRepository.updateUsage(auth.user.id, file.size);
|
||||||
@@ -326,6 +326,12 @@ export class AssetMediaService extends BaseService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async addToSharedLink(sharedLink: AuthSharedLink, assetId: string) {
|
||||||
|
await (sharedLink.albumId
|
||||||
|
? this.albumRepository.addAssetIds(sharedLink.albumId, [assetId])
|
||||||
|
: this.sharedLinkRepository.addAssets(sharedLink.id, [assetId]));
|
||||||
|
}
|
||||||
|
|
||||||
private async handleUploadError(
|
private async handleUploadError(
|
||||||
error: any,
|
error: any,
|
||||||
auth: AuthDto,
|
auth: AuthDto,
|
||||||
@@ -347,7 +353,7 @@ export class AssetMediaService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auth.sharedLink) {
|
if (auth.sharedLink) {
|
||||||
await this.sharedLinkRepository.addAssets(auth.sharedLink.id, [duplicateId]);
|
await this.addToSharedLink(auth.sharedLink, duplicateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status: AssetMediaStatus.DUPLICATE, id: duplicateId };
|
return { status: AssetMediaStatus.DUPLICATE, id: duplicateId };
|
||||||
|
|||||||
1
server/test/fixtures/auth.stub.ts
vendored
1
server/test/fixtures/auth.stub.ts
vendored
@@ -48,6 +48,7 @@ export const authStub = {
|
|||||||
showExif: true,
|
showExif: true,
|
||||||
allowDownload: true,
|
allowDownload: true,
|
||||||
allowUpload: true,
|
allowUpload: true,
|
||||||
|
albumId: null,
|
||||||
expiresAt: null,
|
expiresAt: null,
|
||||||
password: null,
|
password: null,
|
||||||
userId: '42',
|
userId: '42',
|
||||||
|
|||||||
@@ -220,9 +220,9 @@ export class MediumTestContext<S extends BaseService = BaseService> {
|
|||||||
return { result };
|
return { result };
|
||||||
}
|
}
|
||||||
|
|
||||||
async newAlbum(dto: Insertable<AlbumTable>) {
|
async newAlbum(dto: Insertable<AlbumTable>, assetIds?: string[]) {
|
||||||
const album = mediumFactory.albumInsert(dto);
|
const album = mediumFactory.albumInsert(dto);
|
||||||
const result = await this.get(AlbumRepository).create(album, [], []);
|
const result = await this.get(AlbumRepository).create(album, assetIds ?? [], []);
|
||||||
return { album, result };
|
return { album, result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { Kysely } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
import { AssetMediaStatus } from 'src/dtos/asset-media-response.dto';
|
import { AssetMediaStatus } from 'src/dtos/asset-media-response.dto';
|
||||||
import { AssetMediaSize } from 'src/dtos/asset-media.dto';
|
import { AssetMediaSize } from 'src/dtos/asset-media.dto';
|
||||||
import { AssetFileType } from 'src/enum';
|
import { AssetFileType, SharedLinkType } from 'src/enum';
|
||||||
import { AccessRepository } from 'src/repositories/access.repository';
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
|
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||||
import { EventRepository } from 'src/repositories/event.repository';
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
import { JobRepository } from 'src/repositories/job.repository';
|
import { JobRepository } from 'src/repositories/job.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { SharedLinkRepository } from 'src/repositories/shared-link.repository';
|
||||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||||
import { UserRepository } from 'src/repositories/user.repository';
|
import { UserRepository } from 'src/repositories/user.repository';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
@@ -22,7 +25,7 @@ let defaultDatabase: Kysely<DB>;
|
|||||||
const setup = (db?: Kysely<DB>) => {
|
const setup = (db?: Kysely<DB>) => {
|
||||||
return newMediumService(AssetMediaService, {
|
return newMediumService(AssetMediaService, {
|
||||||
database: db || defaultDatabase,
|
database: db || defaultDatabase,
|
||||||
real: [AccessRepository, AssetRepository, UserRepository],
|
real: [AccessRepository, AlbumRepository, AssetRepository, SharedLinkRepository, UserRepository],
|
||||||
mock: [EventRepository, LoggingRepository, JobRepository, StorageRepository],
|
mock: [EventRepository, LoggingRepository, JobRepository, StorageRepository],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -44,7 +47,6 @@ describe(AssetService.name, () => {
|
|||||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 });
|
await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 });
|
||||||
const auth = factory.auth({ user: { id: user.id } });
|
const auth = factory.auth({ user: { id: user.id } });
|
||||||
const file = mediumFactory.uploadFile();
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.uploadAsset(
|
sut.uploadAsset(
|
||||||
@@ -56,7 +58,7 @@ describe(AssetService.name, () => {
|
|||||||
fileCreatedAt: new Date(),
|
fileCreatedAt: new Date(),
|
||||||
assetData: Buffer.from('some data'),
|
assetData: Buffer.from('some data'),
|
||||||
},
|
},
|
||||||
file,
|
mediumFactory.uploadFile(),
|
||||||
),
|
),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
@@ -99,6 +101,168 @@ describe(AssetService.name, () => {
|
|||||||
status: AssetMediaStatus.CREATED,
|
status: AssetMediaStatus.CREATED,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add to a shared link', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
|
||||||
|
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
||||||
|
|
||||||
|
ctx.getMock(StorageRepository).utimes.mockResolvedValue();
|
||||||
|
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||||
|
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||||
|
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
|
||||||
|
const sharedLink = await sharedLinkRepo.create({
|
||||||
|
key: randomBytes(50),
|
||||||
|
type: SharedLinkType.Individual,
|
||||||
|
description: 'Shared link description',
|
||||||
|
userId: user.id,
|
||||||
|
allowDownload: true,
|
||||||
|
allowUpload: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const auth = factory.auth({ user: { id: user.id }, sharedLink });
|
||||||
|
const file = mediumFactory.uploadFile();
|
||||||
|
const uploadDto = {
|
||||||
|
deviceId: 'some-id',
|
||||||
|
deviceAssetId: 'some-id',
|
||||||
|
fileModifiedAt: new Date(),
|
||||||
|
fileCreatedAt: new Date(),
|
||||||
|
assetData: Buffer.from('some data'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await sut.uploadAsset(auth, uploadDto, file);
|
||||||
|
expect(response).toEqual({ id: expect.any(String), status: AssetMediaStatus.CREATED });
|
||||||
|
|
||||||
|
const update = await sharedLinkRepo.get(user.id, sharedLink.id);
|
||||||
|
const assets = update!.assets;
|
||||||
|
expect(assets).toHaveLength(1);
|
||||||
|
expect(assets[0]).toMatchObject({ id: response.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle adding a duplicate asset to a shared link', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
|
||||||
|
ctx.getMock(StorageRepository).utimes.mockResolvedValue();
|
||||||
|
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||||
|
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||||
|
|
||||||
|
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
||||||
|
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 });
|
||||||
|
|
||||||
|
const sharedLink = await sharedLinkRepo.create({
|
||||||
|
key: randomBytes(50),
|
||||||
|
type: SharedLinkType.Individual,
|
||||||
|
description: 'Shared link description',
|
||||||
|
userId: user.id,
|
||||||
|
allowDownload: true,
|
||||||
|
allowUpload: true,
|
||||||
|
assetIds: [asset.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
const auth = factory.auth({ user: { id: user.id }, sharedLink });
|
||||||
|
const uploadDto = {
|
||||||
|
deviceId: 'some-id',
|
||||||
|
deviceAssetId: 'some-id',
|
||||||
|
fileModifiedAt: new Date(),
|
||||||
|
fileCreatedAt: new Date(),
|
||||||
|
assetData: Buffer.from('some data'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await sut.uploadAsset(auth, uploadDto, mediumFactory.uploadFile({ checksum: asset.checksum }));
|
||||||
|
expect(response).toEqual({ id: expect.any(String), status: AssetMediaStatus.DUPLICATE });
|
||||||
|
|
||||||
|
const update = await sharedLinkRepo.get(user.id, sharedLink.id);
|
||||||
|
const assets = update!.assets;
|
||||||
|
expect(assets).toHaveLength(1);
|
||||||
|
expect(assets[0]).toMatchObject({ id: response.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add to an album shared link', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
|
||||||
|
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
||||||
|
|
||||||
|
ctx.getMock(StorageRepository).utimes.mockResolvedValue();
|
||||||
|
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||||
|
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||||
|
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
||||||
|
|
||||||
|
const sharedLink = await sharedLinkRepo.create({
|
||||||
|
key: randomBytes(50),
|
||||||
|
type: SharedLinkType.Album,
|
||||||
|
albumId: album.id,
|
||||||
|
description: 'Shared link description',
|
||||||
|
userId: user.id,
|
||||||
|
allowDownload: true,
|
||||||
|
allowUpload: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const auth = factory.auth({ user: { id: user.id }, sharedLink });
|
||||||
|
const uploadDto = {
|
||||||
|
deviceId: 'some-id',
|
||||||
|
deviceAssetId: 'some-id',
|
||||||
|
fileModifiedAt: new Date(),
|
||||||
|
fileCreatedAt: new Date(),
|
||||||
|
assetData: Buffer.from('some data'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await sut.uploadAsset(auth, uploadDto, mediumFactory.uploadFile());
|
||||||
|
expect(response).toEqual({ id: expect.any(String), status: AssetMediaStatus.CREATED });
|
||||||
|
|
||||||
|
const result = await ctx.get(AlbumRepository).getAssetIds(album.id, [response.id]);
|
||||||
|
const assets = [...result];
|
||||||
|
expect(assets).toHaveLength(1);
|
||||||
|
expect(assets[0]).toEqual(response.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle adding a duplicate asset to an album shared link', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
|
||||||
|
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
||||||
|
|
||||||
|
ctx.getMock(StorageRepository).utimes.mockResolvedValue();
|
||||||
|
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||||
|
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||||
|
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
const { album } = await ctx.newAlbum({ ownerId: user.id }, [asset.id]);
|
||||||
|
// await ctx.newExif({ assetId: asset.id, fileSizeInByte: 12_345 });
|
||||||
|
|
||||||
|
const sharedLink = await sharedLinkRepo.create({
|
||||||
|
key: randomBytes(50),
|
||||||
|
type: SharedLinkType.Album,
|
||||||
|
albumId: album.id,
|
||||||
|
description: 'Shared link description',
|
||||||
|
userId: user.id,
|
||||||
|
allowDownload: true,
|
||||||
|
allowUpload: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const auth = factory.auth({ user: { id: user.id }, sharedLink });
|
||||||
|
const uploadDto = {
|
||||||
|
deviceId: 'some-id',
|
||||||
|
deviceAssetId: 'some-id',
|
||||||
|
fileModifiedAt: new Date(),
|
||||||
|
fileCreatedAt: new Date(),
|
||||||
|
assetData: Buffer.from('some data'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await sut.uploadAsset(auth, uploadDto, mediumFactory.uploadFile({ checksum: asset.checksum }));
|
||||||
|
expect(response).toEqual({ id: expect.any(String), status: AssetMediaStatus.DUPLICATE });
|
||||||
|
|
||||||
|
const result = await ctx.get(AlbumRepository).getAssetIds(album.id, [response.id]);
|
||||||
|
const assets = [...result];
|
||||||
|
expect(assets).toHaveLength(1);
|
||||||
|
expect(assets[0]).toEqual(response.id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('viewThumbnail', () => {
|
describe('viewThumbnail', () => {
|
||||||
|
|||||||
@@ -63,12 +63,22 @@ const authSharedLinkFactory = (sharedLink: Partial<AuthSharedLink> = {}) => {
|
|||||||
expiresAt = null,
|
expiresAt = null,
|
||||||
userId = newUuid(),
|
userId = newUuid(),
|
||||||
showExif = true,
|
showExif = true,
|
||||||
|
albumId = null,
|
||||||
allowUpload = false,
|
allowUpload = false,
|
||||||
allowDownload = true,
|
allowDownload = true,
|
||||||
password = null,
|
password = null,
|
||||||
} = sharedLink;
|
} = sharedLink;
|
||||||
|
|
||||||
return { id, expiresAt, userId, showExif, allowUpload, allowDownload, password };
|
return {
|
||||||
|
id,
|
||||||
|
albumId,
|
||||||
|
expiresAt,
|
||||||
|
userId,
|
||||||
|
showExif,
|
||||||
|
allowUpload,
|
||||||
|
allowDownload,
|
||||||
|
password,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const authApiKeyFactory = (apiKey: Partial<AuthApiKey> = {}) => ({
|
const authApiKeyFactory = (apiKey: Partial<AuthApiKey> = {}) => ({
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ async function fileUploader({
|
|||||||
uploadAssetsStore.track('success');
|
uploadAssetsStore.track('success');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (albumId) {
|
if (albumId && !authManager.isSharedLink) {
|
||||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_adding_to_album') });
|
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_adding_to_album') });
|
||||||
await addAssetsToAlbums([albumId], [responseData.id], { notify: false });
|
await addAssetsToAlbums([albumId], [responseData.id], { notify: false });
|
||||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_added_to_album') });
|
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_added_to_album') });
|
||||||
|
|||||||
Reference in New Issue
Block a user