Compare commits
5 Commits
qr-code-lo
...
v1.128.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc143046e3 | ||
|
|
e684062569 | ||
|
|
5c0538e52c | ||
|
|
84cf0d1670 | ||
|
|
bfcde05b1c |
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.51",
|
"version": "2.2.52",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.51",
|
"version": "2.2.52",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.51",
|
"version": "2.2.52",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
|
|||||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,4 +1,8 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v1.128.0",
|
||||||
|
"url": "https://v1.128.0.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v1.127.0",
|
"label": "v1.127.0",
|
||||||
"url": "https://v1.127.0.archive.immich.app"
|
"url": "https://v1.127.0.archive.immich.app"
|
||||||
|
|||||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
},
|
},
|
||||||
"../cli": {
|
"../cli": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.51",
|
"version": "2.2.52",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LoginResponseDto, getAssetInfo, getAssetStatistics, scanLibrary } from '@immich/sdk';
|
import { LoginResponseDto, getAssetInfo, getAssetStatistics } from '@immich/sdk';
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
@@ -6,8 +6,6 @@ import { app, asBearerAuth, testAssetDir, testAssetDirInternal, utils } from 'sr
|
|||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
const scan = async (accessToken: string, id: string) => scanLibrary({ id }, { headers: asBearerAuth(accessToken) });
|
|
||||||
|
|
||||||
describe('/trash', () => {
|
describe('/trash', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let ws: Socket;
|
let ws: Socket;
|
||||||
@@ -81,8 +79,7 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await utils.scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
|
||||||
|
|
||||||
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
||||||
expect(assets.items.length).toBe(1);
|
expect(assets.items.length).toBe(1);
|
||||||
@@ -90,8 +87,7 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await utils.scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
|
||||||
|
|
||||||
const assetBefore = await utils.getAssetInfo(admin.accessToken, asset.id);
|
const assetBefore = await utils.getAssetInfo(admin.accessToken, asset.id);
|
||||||
expect(assetBefore).toMatchObject({ isTrashed: true, isOffline: true });
|
expect(assetBefore).toMatchObject({ isTrashed: true, isOffline: true });
|
||||||
@@ -116,8 +112,7 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await utils.scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
|
||||||
|
|
||||||
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
||||||
expect(assets.items.length).toBe(1);
|
expect(assets.items.length).toBe(1);
|
||||||
@@ -125,8 +120,7 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await utils.scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
|
||||||
|
|
||||||
const assetBefore = await utils.getAssetInfo(admin.accessToken, asset.id);
|
const assetBefore = await utils.getAssetInfo(admin.accessToken, asset.id);
|
||||||
expect(assetBefore).toMatchObject({ isTrashed: true, isOffline: true });
|
expect(assetBefore).toMatchObject({ isTrashed: true, isOffline: true });
|
||||||
@@ -180,8 +174,7 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await utils.scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
|
||||||
|
|
||||||
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
||||||
expect(assets.count).toBe(1);
|
expect(assets.count).toBe(1);
|
||||||
@@ -189,9 +182,7 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await utils.scan(admin.accessToken, library.id);
|
||||||
|
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
|
||||||
|
|
||||||
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
||||||
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
|
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
|
||||||
@@ -201,6 +192,8 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
||||||
expect(after).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
|
expect(after).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
|
||||||
|
|
||||||
|
utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -238,7 +231,7 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await utils.scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
|
||||||
@@ -247,7 +240,7 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
|
||||||
|
|
||||||
await scan(admin.accessToken, library.id);
|
await utils.scan(admin.accessToken, library.id);
|
||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
const before = await utils.getAssetInfo(admin.accessToken, assetId);
|
const before = await utils.getAssetInfo(admin.accessToken, assetId);
|
||||||
@@ -261,6 +254,8 @@ describe('/trash', () => {
|
|||||||
|
|
||||||
const after = await utils.getAssetInfo(admin.accessToken, assetId);
|
const after = await utils.getAssetInfo(admin.accessToken, assetId);
|
||||||
expect(after.isTrashed).toBe(true);
|
expect(after.isTrashed).toBe(true);
|
||||||
|
|
||||||
|
utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.127.0"
|
version = "1.128.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 185,
|
"android.injected.version.code" => 186,
|
||||||
"android.injected.version.name" => "1.127.0",
|
"android.injected.version.name" => "1.128.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Release"
|
desc "iOS Release"
|
||||||
lane :release do
|
lane :release do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.127.0"
|
version_number: "1.128.0"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@@ -3,7 +3,7 @@ Immich API
|
|||||||
|
|
||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 1.127.0
|
- API version: 1.128.0
|
||||||
- Generator version: 7.8.0
|
- Generator version: 7.8.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.127.0+185
|
version: 1.128.0+186
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
|||||||
@@ -7655,7 +7655,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
|||||||
4
open-api/typescript-sdk/package-lock.json
generated
4
open-api/typescript-sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.127.0
|
* 1.128.0
|
||||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||||
* See https://www.npmjs.com/package/oazapfts
|
* See https://www.npmjs.com/package/oazapfts
|
||||||
*/
|
*/
|
||||||
|
|||||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/bullmq": "^11.0.1",
|
"@nestjs/bullmq": "^11.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -55,9 +55,10 @@ with
|
|||||||
inner join "exif" on "a"."id" = "exif"."assetId"
|
inner join "exif" on "a"."id" = "exif"."assetId"
|
||||||
)
|
)
|
||||||
select
|
select
|
||||||
(
|
date_part(
|
||||||
(now() at time zone 'UTC')::date - ("localDateTime" at time zone 'UTC')::date
|
'year',
|
||||||
) / 365 as "yearsAgo",
|
("localDateTime" at time zone 'UTC')::date
|
||||||
|
)::int as "year",
|
||||||
json_agg("res") as "assets"
|
json_agg("res") as "assets"
|
||||||
from
|
from
|
||||||
"res"
|
"res"
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ export class AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
|
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
|
||||||
getByDayOfYear(ownerIds: string[], { day, month }: MonthDay): Promise<DayOfYearAssets[]> {
|
getByDayOfYear(ownerIds: string[], { day, month }: MonthDay) {
|
||||||
return this.db
|
return this.db
|
||||||
.with('res', (qb) =>
|
.with('res', (qb) =>
|
||||||
qb
|
qb
|
||||||
@@ -239,16 +239,12 @@ export class AssetRepository {
|
|||||||
.select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')),
|
.select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')),
|
||||||
)
|
)
|
||||||
.selectFrom('res')
|
.selectFrom('res')
|
||||||
.select(
|
.select(sql<number>`date_part('year', ("localDateTime" at time zone 'UTC')::date)::int`.as('year'))
|
||||||
sql<number>`((now() at time zone 'UTC')::date - ("localDateTime" at time zone 'UTC')::date) / 365`.as(
|
|
||||||
'yearsAgo',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.select((eb) => eb.fn.jsonAgg(eb.table('res')).as('assets'))
|
.select((eb) => eb.fn.jsonAgg(eb.table('res')).as('assets'))
|
||||||
.groupBy(sql`("localDateTime" at time zone 'UTC')::date`)
|
.groupBy(sql`("localDateTime" at time zone 'UTC')::date`)
|
||||||
.orderBy(sql`("localDateTime" at time zone 'UTC')::date`, 'desc')
|
.orderBy(sql`("localDateTime" at time zone 'UTC')::date`, 'desc')
|
||||||
.limit(10)
|
.limit(10)
|
||||||
.execute() as any as Promise<DayOfYearAssets[]>;
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [[DummyValue.UUID]] })
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||||
|
|||||||
@@ -64,18 +64,18 @@ describe(AssetService.name, () => {
|
|||||||
mocks.partner.getAll.mockResolvedValue([]);
|
mocks.partner.getAll.mockResolvedValue([]);
|
||||||
mocks.asset.getByDayOfYear.mockResolvedValue([
|
mocks.asset.getByDayOfYear.mockResolvedValue([
|
||||||
{
|
{
|
||||||
yearsAgo: 1,
|
year: 2023,
|
||||||
assets: [image1, image2],
|
assets: [image1, image2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
yearsAgo: 9,
|
year: 2015,
|
||||||
assets: [image3],
|
assets: [image3],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
yearsAgo: 15,
|
year: 2009,
|
||||||
assets: [image4],
|
assets: [image4],
|
||||||
},
|
},
|
||||||
]);
|
] as any);
|
||||||
|
|
||||||
await expect(sut.getMemoryLane(authStub.admin, { day: 15, month: 1 })).resolves.toEqual([
|
await expect(sut.getMemoryLane(authStub.admin, { day: 15, month: 1 })).resolves.toEqual([
|
||||||
{ yearsAgo: 1, title: '1 year ago', assets: [mapAsset(image1), mapAsset(image2)] },
|
{ yearsAgo: 1, title: '1 year ago', assets: [mapAsset(image1), mapAsset(image2)] },
|
||||||
|
|||||||
@@ -38,12 +38,15 @@ export class AssetService extends BaseService {
|
|||||||
const userIds = [auth.user.id, ...partnerIds];
|
const userIds = [auth.user.id, ...partnerIds];
|
||||||
|
|
||||||
const groups = await this.assetRepository.getByDayOfYear(userIds, dto);
|
const groups = await this.assetRepository.getByDayOfYear(userIds, dto);
|
||||||
return groups.map(({ yearsAgo, assets }) => ({
|
return groups.map(({ year, assets }) => {
|
||||||
yearsAgo,
|
const yearsAgo = DateTime.utc().year - year;
|
||||||
// TODO move this to clients
|
return {
|
||||||
title: `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} ago`,
|
yearsAgo,
|
||||||
assets: assets.map((asset) => mapAsset(asset, { auth })),
|
// TODO move this to clients
|
||||||
}));
|
title: `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} ago`,
|
||||||
|
assets: assets.map((asset) => mapAsset(asset as AssetEntity, { auth })),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStatistics(auth: AuthDto, dto: AssetStatsDto) {
|
async getStatistics(auth: AuthDto, dto: AssetStatsDto) {
|
||||||
|
|||||||
@@ -195,7 +195,11 @@ export class JobService extends BaseService {
|
|||||||
await this.onDone(job);
|
await this.onDone(job);
|
||||||
}
|
}
|
||||||
} catch (error: Error | any) {
|
} catch (error: Error | any) {
|
||||||
this.logger.error(`Unable to run job handler (${queueName}/${job.name}): ${error}`, error?.stack, job.data);
|
this.logger.error(
|
||||||
|
`Unable to run job handler (${queueName}/${job.name}): ${error}`,
|
||||||
|
error?.stack,
|
||||||
|
JSON.stringify(job.data),
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.telemetryRepository.jobs.addToGauge(queueMetric, -1);
|
this.telemetryRepository.jobs.addToGauge(queueMetric, -1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,35 +30,33 @@ export class MemoryService extends BaseService {
|
|||||||
const start = DateTime.utc().startOf('day').minus({ days: DAYS });
|
const start = DateTime.utc().startOf('day').minus({ days: DAYS });
|
||||||
|
|
||||||
const state = await this.systemMetadataRepository.get(SystemMetadataKey.MEMORIES_STATE);
|
const state = await this.systemMetadataRepository.get(SystemMetadataKey.MEMORIES_STATE);
|
||||||
let lastOnThisDayDate = state?.lastOnThisDayDate ? DateTime.fromISO(state?.lastOnThisDayDate) : start;
|
const lastOnThisDayDate = state?.lastOnThisDayDate ? DateTime.fromISO(state.lastOnThisDayDate) : start;
|
||||||
|
|
||||||
// generate a memory +/- X days from today
|
// generate a memory +/- X days from today
|
||||||
for (let i = 0; i <= DAYS * 2 + 1; i++) {
|
for (let i = 0; i <= DAYS * 2; i++) {
|
||||||
const target = start.plus({ days: i });
|
const target = start.plus({ days: i });
|
||||||
if (lastOnThisDayDate > target) {
|
if (lastOnThisDayDate >= target) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showAt = target.startOf('day').toISO();
|
const showAt = target.startOf('day').toISO();
|
||||||
const hideAt = target.endOf('day').toISO();
|
const hideAt = target.endOf('day').toISO();
|
||||||
|
|
||||||
this.logger.log(`Creating memories for month=${target.month}, day=${target.day}`);
|
|
||||||
|
|
||||||
for (const [userId, userIds] of Object.entries(userMap)) {
|
for (const [userId, userIds] of Object.entries(userMap)) {
|
||||||
const memories = await this.assetRepository.getByDayOfYear(userIds, target);
|
const memories = await this.assetRepository.getByDayOfYear(userIds, target);
|
||||||
|
|
||||||
for (const memory of memories) {
|
for (const { year, assets } of memories) {
|
||||||
const data: OnThisDayData = { year: target.year - memory.yearsAgo };
|
const data: OnThisDayData = { year };
|
||||||
await this.memoryRepository.create(
|
await this.memoryRepository.create(
|
||||||
{
|
{
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
type: MemoryType.ON_THIS_DAY,
|
type: MemoryType.ON_THIS_DAY,
|
||||||
data,
|
data,
|
||||||
memoryAt: target.minus({ years: memory.yearsAgo }).toISO(),
|
memoryAt: target.set({ year }).toISO(),
|
||||||
showAt,
|
showAt,
|
||||||
hideAt,
|
hideAt,
|
||||||
},
|
},
|
||||||
new Set(memory.assets.map(({ id }) => id)),
|
new Set(assets.map(({ id }) => id)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,8 +65,6 @@ export class MemoryService extends BaseService {
|
|||||||
...state,
|
...state,
|
||||||
lastOnThisDayDate: target.toISO(),
|
lastOnThisDayDate: target.toISO(),
|
||||||
});
|
});
|
||||||
|
|
||||||
lastOnThisDayDate = target;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.127.0",
|
"version": "1.128.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
||||||
|
|||||||
@@ -8,14 +8,12 @@
|
|||||||
import { preferences, user } from '$lib/stores/user.store';
|
import { preferences, user } from '$lib/stores/user.store';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk';
|
import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk';
|
||||||
import { mdiCog, mdiLogout, mdiPencil, mdiQrcode, mdiWrench } from '@mdi/js';
|
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { NotificationType, notificationController } from '../notification/notification';
|
import { NotificationType, notificationController } from '../notification/notification';
|
||||||
import UserAvatar from '../user-avatar.svelte';
|
import UserAvatar from '../user-avatar.svelte';
|
||||||
import AvatarSelector from './avatar-selector.svelte';
|
import AvatarSelector from './avatar-selector.svelte';
|
||||||
import { IconButton } from '@immich/ui';
|
|
||||||
import { qrCodeLoginStore } from '$lib/stores/qrcode-login.svelte';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onLogout: () => void;
|
onLogout: () => void;
|
||||||
@@ -53,14 +51,6 @@
|
|||||||
class="absolute right-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
|
class="absolute right-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
|
||||||
use:focusTrap
|
use:focusTrap
|
||||||
>
|
>
|
||||||
<div class="absolute right-8 top-8">
|
|
||||||
<IconButton
|
|
||||||
icon={mdiQrcode}
|
|
||||||
shape="round"
|
|
||||||
aria-label="Signin With QR Code"
|
|
||||||
onclick={() => qrCodeLoginStore.showForm()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="mx-4 mt-4 flex flex-col items-center justify-center gap-4 rounded-3xl bg-white p-4 dark:bg-immich-dark-primary/10"
|
class="mx-4 mt-4 flex flex-col items-center justify-center gap-4 rounded-3xl bg-white p-4 dark:bg-immich-dark-primary/10"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
|
||||||
import { qrCodeLoginStore } from '$lib/stores/qrcode-login.svelte';
|
|
||||||
import { Button, Input } from '@immich/ui';
|
|
||||||
|
|
||||||
let password = $state('');
|
|
||||||
|
|
||||||
const submitPassword = (event: Event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
console.log('Password submitted:', password);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if qrCodeLoginStore.shouldShowForm}
|
|
||||||
<div id="instance-qr-login">
|
|
||||||
<FullScreenModal title={'Login QR Code'} onClose={() => {}}>
|
|
||||||
<p class="text-xs">Enter your password to show the QR code</p>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<form onsubmit={submitPassword}>
|
|
||||||
<Input size="small" placeholder="Password" type="password" bind:value={password} required />
|
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
<Button type="submit" size="small" shape="round" fullWidth>Confirm</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</FullScreenModal>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
class QRCodeLoginStore {
|
|
||||||
shouldShowForm = $state(false);
|
|
||||||
|
|
||||||
showForm() {
|
|
||||||
this.shouldShowForm = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideForm() {
|
|
||||||
this.shouldShowForm = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const qrCodeLoginStore = new QRCodeLoginStore();
|
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
import { setTranslations } from '@immich/ui';
|
import { setTranslations } from '@immich/ui';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import QRCodeLoginForm from '$lib/components/shared-components/qrcode-login-form.svelte';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
@@ -155,7 +154,6 @@
|
|||||||
<UploadPanel />
|
<UploadPanel />
|
||||||
<NotificationList />
|
<NotificationList />
|
||||||
<DialogWrapper />
|
<DialogWrapper />
|
||||||
<QRCodeLoginForm />
|
|
||||||
|
|
||||||
{#if $user?.isAdmin}
|
{#if $user?.isAdmin}
|
||||||
<VersionAnnouncementBox />
|
<VersionAnnouncementBox />
|
||||||
|
|||||||
Reference in New Issue
Block a user