Merge branch 'main' into feat/mobile-ocr

This commit is contained in:
Yaros
2026-04-13 18:07:43 +02:00
committed by GitHub
417 changed files with 13493 additions and 14010 deletions
@@ -115,4 +115,33 @@ describe(AssetJobRepository.name, () => {
);
});
});
describe('getForOcr', () => {
it('should not return the edited preview file', async () => {
const { ctx, sut } = setup();
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id });
await ctx.newAssetFile({
assetId: asset.id,
type: AssetFileType.Preview,
path: 'preview_edited.jpg',
isEdited: true,
});
await ctx.newAssetFile({
assetId: asset.id,
type: AssetFileType.Preview,
path: 'preview_unedited.jpg',
isEdited: false,
});
const result = await sut.getForOcr(asset.id);
expect(result).toEqual(
expect.objectContaining({
previewFile: 'preview_unedited.jpg',
}),
);
});
});
});
@@ -5,6 +5,7 @@ import { AccessRepository } from 'src/repositories/access.repository';
import { AssetEditRepository } from 'src/repositories/asset-edit.repository';
import { AssetRepository } from 'src/repositories/asset.repository';
import { DatabaseRepository } from 'src/repositories/database.repository';
import { JobRepository } from 'src/repositories/job.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { PersonRepository } from 'src/repositories/person.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
@@ -20,7 +21,7 @@ const setup = (db?: Kysely<DB>) => {
return newMediumService(PersonService, {
database: db || defaultDatabase,
real: [AccessRepository, DatabaseRepository, PersonRepository, AssetRepository, AssetEditRepository],
mock: [LoggingRepository, StorageRepository],
mock: [JobRepository, LoggingRepository, StorageRepository],
});
};
@@ -89,6 +90,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 200, height: 200 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 200 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
const auth = factory.auth({ user });
@@ -128,6 +130,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 150, height: 200 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 200 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -199,6 +202,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 100, height: 200 });
await ctx.newExif({ assetId: asset.id, exifImageWidth: 200, exifImageHeight: 100 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -263,6 +267,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 200, height: 100 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 100, exifImageWidth: 200 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -327,6 +332,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 200, height: 150 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 200 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -400,6 +406,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 150, height: 100 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 100, exifImageWidth: 200 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -473,6 +480,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 200, height: 150 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 150 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -543,6 +551,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 150, height: 100 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 200 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -622,6 +631,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 100, height: 100 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 100, exifImageWidth: 100 });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -692,6 +702,7 @@ describe(PersonService.name, () => {
const { person } = await ctx.newPerson({ ownerId: user.id });
const { asset } = await ctx.newAsset({ id: factory.uuid(), ownerId: user.id, width: 100, height: 100 });
await ctx.newExif({ assetId: asset.id, exifImageHeight: 200, exifImageWidth: 100, orientation: '6' });
ctx.getMock(JobRepository).queueAll.mockResolvedValue();
await ctx.newEdits(asset.id, {
edits: [
@@ -1,4 +1,5 @@
import { Kysely } from 'kysely';
import { SearchSuggestionType } from 'src/dtos/search.dto';
import { AccessRepository } from 'src/repositories/access.repository';
import { AssetRepository } from 'src/repositories/asset.repository';
import { DatabaseRepository } from 'src/repositories/database.repository';
@@ -108,4 +109,25 @@ describe(SearchService.name, () => {
expect(response.assets.items[0].id).toBe(unstackedAsset.id);
});
});
describe('getSearchSuggestions', () => {
it('should filter out empty search suggestions', async () => {
const { sut, ctx } = setup();
const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id });
await ctx.newExif({ assetId: asset.id, make: 'Canon' });
const { asset: assetWithEmptyMake } = await ctx.newAsset({ ownerId: user.id });
await ctx.newExif({ assetId: assetWithEmptyMake.id, make: '' });
const auth = factory.auth({ user: { id: user.id } });
const suggestions = await sut.getSearchSuggestions(auth, {
type: SearchSuggestionType.CAMERA_MAKE,
includeNull: true,
});
expect(suggestions).toEqual(['Canon', null]);
});
});
});
@@ -372,6 +372,43 @@ describe(SharedLinkService.name, () => {
});
describe('get', () => {
it('should return an album shared link with assets', async () => {
const { sut, ctx } = setup();
const { user } = await ctx.newUser();
const auth = factory.auth({ user });
const { album } = await ctx.newAlbum({ ownerId: user.id });
const [{ asset: asset1 }, { asset: asset2 }] = await Promise.all([
ctx.newAsset({ ownerId: user.id }),
ctx.newAsset({ ownerId: user.id }),
]);
await Promise.all([
ctx.newExif({ assetId: asset1.id, make: 'Canon' }),
ctx.newExif({ assetId: asset2.id, make: 'Canon' }),
]);
const sharedLinkRepo = ctx.get(SharedLinkRepository);
const sharedLink = await sharedLinkRepo.create({
key: randomBytes(16),
id: factory.uuid(),
userId: user.id,
albumId: album.id,
allowUpload: true,
type: SharedLinkType.Album,
});
await sharedLinkRepo.addAssets(sharedLink.id, [asset1.id, asset2.id]);
const result = await sut.get(auth, sharedLink.id);
const assetIds = result.assets.map((asset) => asset.id);
expect(result).toMatchObject({
id: sharedLink.id,
album: expect.objectContaining({ id: album.id }),
});
expect(assetIds).toHaveLength(2);
expect(assetIds).toEqual(expect.arrayContaining([asset1.id, asset2.id]));
});
it('should not return trashed assets for an individual shared link', async () => {
const { sut, ctx } = setup();
const { user } = await ctx.newUser();
@@ -1,6 +1,7 @@
import { Kysely } from 'kysely';
import { serverVersion } from 'src/constants';
import { JobName } from 'src/enum';
import { CronRepository } from 'src/repositories/cron.repository';
import { DatabaseRepository } from 'src/repositories/database.repository';
import { JobRepository } from 'src/repositories/job.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
@@ -16,7 +17,7 @@ const setup = (db?: Kysely<DB>) => {
return newMediumService(VersionService, {
database: db || defaultDatabase,
real: [DatabaseRepository, VersionHistoryRepository],
mock: [LoggingRepository, JobRepository],
mock: [LoggingRepository, JobRepository, CronRepository],
});
};