Compare commits
1 Commits
v1.125.7
...
asset-user
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7277ea3d7a |
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.48",
|
||||
"version": "2.2.47",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.48",
|
||||
"version": "2.2.47",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"fast-glob": "^3.3.2",
|
||||
@@ -52,7 +52,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.48",
|
||||
"version": "2.2.47",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
|
||||
@@ -48,7 +48,6 @@ services:
|
||||
vaapi-wsl: # use this for VAAPI if you're running Immich in WSL2
|
||||
devices:
|
||||
- /dev/dri:/dev/dri
|
||||
- /dev/dxg:/dev/dxg
|
||||
volumes:
|
||||
- /usr/lib/wsl:/usr/lib/wsl
|
||||
environment:
|
||||
|
||||
@@ -8,23 +8,22 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
|
||||
|
||||
## Image formats
|
||||
|
||||
| Format | Extension(s) | Supported? | Notes |
|
||||
| :---------- | :---------------------------- | :----------------: | :-------------- |
|
||||
| `AVIF` | `.avif` | :white_check_mark: | |
|
||||
| `BMP` | `.bmp` | :white_check_mark: | |
|
||||
| `GIF` | `.gif` | :white_check_mark: | |
|
||||
| `HEIC` | `.heic` | :white_check_mark: | |
|
||||
| `HEIF` | `.heif` | :white_check_mark: | |
|
||||
| `JPEG 2000` | `.jp2` | :white_check_mark: | |
|
||||
| `JPEG` | `.webp` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
|
||||
| `JPEG XL` | `.jxl` | :white_check_mark: | |
|
||||
| `PNG` | `.webp` | :white_check_mark: | |
|
||||
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
|
||||
| `RAW` | `.raw` | :white_check_mark: | |
|
||||
| `RW2` | `.rw2` | :white_check_mark: | |
|
||||
| `SVG` | `.svg` | :white_check_mark: | |
|
||||
| `TIFF` | `.tif` `.tiff` | :white_check_mark: | |
|
||||
| `WEBP` | `.webp` | :white_check_mark: | |
|
||||
| Format | Extension(s) | Supported? | Notes |
|
||||
| :-------- | :---------------------------- | :----------------: | :-------------- |
|
||||
| `AVIF` | `.avif` | :white_check_mark: | |
|
||||
| `BMP` | `.bmp` | :white_check_mark: | |
|
||||
| `GIF` | `.gif` | :white_check_mark: | |
|
||||
| `HEIC` | `.heic` | :white_check_mark: | |
|
||||
| `HEIF` | `.heif` | :white_check_mark: | |
|
||||
| `JPEG` | `.webp` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
|
||||
| `JPEG XL` | `.jxl` | :white_check_mark: | |
|
||||
| `PNG` | `.webp` | :white_check_mark: | |
|
||||
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
|
||||
| `RAW` | `.raw` | :white_check_mark: | |
|
||||
| `RW2` | `.rw2` | :white_check_mark: | |
|
||||
| `SVG` | `.svg` | :white_check_mark: | |
|
||||
| `TIFF` | `.tif` `.tiff` | :white_check_mark: | |
|
||||
| `WEBP` | `.webp` | :white_check_mark: | |
|
||||
|
||||
## Video formats
|
||||
|
||||
|
||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,8 +1,4 @@
|
||||
[
|
||||
{
|
||||
"label": "v1.125.7",
|
||||
"url": "https://v1.125.7.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v1.125.6",
|
||||
"url": "https://v1.125.6.archive.immich.app"
|
||||
|
||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-e2e",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"../cli": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.48",
|
||||
"version": "2.2.47",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
@@ -92,7 +92,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -356,24 +356,5 @@ describe('/admin/users', () => {
|
||||
expect(status).toBe(403);
|
||||
expect(body).toEqual(errorDto.forbidden);
|
||||
});
|
||||
|
||||
it('should restore a user', async () => {
|
||||
const user = await utils.userSetup(admin.accessToken, createUserDto.create('restore'));
|
||||
|
||||
await deleteUserAdmin({ id: user.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) });
|
||||
|
||||
const { status, body } = await request(app)
|
||||
.post(`/admin/users/${user.userId}/restore`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual(
|
||||
expect.objectContaining({
|
||||
id: user.userId,
|
||||
email: user.userEmail,
|
||||
status: 'active',
|
||||
deletedAt: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "machine-learning"
|
||||
version = "1.125.7"
|
||||
version = "1.125.6"
|
||||
description = ""
|
||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -36,7 +36,7 @@ platform :android do
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 182,
|
||||
"android.injected.version.name" => "1.125.7",
|
||||
"android.injected.version.name" => "1.125.6",
|
||||
}
|
||||
)
|
||||
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"
|
||||
lane :release do
|
||||
increment_version_number(
|
||||
version_number: "1.125.7"
|
||||
version_number: "1.125.6"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
10
mobile/openapi/README.md
generated
10
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:
|
||||
|
||||
- API version: 1.125.7
|
||||
- API version: 1.125.6
|
||||
- Generator version: 7.8.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
@@ -93,17 +93,17 @@ Class | Method | HTTP request | Description
|
||||
*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} |
|
||||
*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} |
|
||||
*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} |
|
||||
*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | checkBulkUpload
|
||||
*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | checkExistingAssets
|
||||
*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | Checks if assets exist by checksums
|
||||
*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | Checks if multiple assets exist on the server and returns all existing - used by background backup
|
||||
*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets |
|
||||
*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original |
|
||||
*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | getAllUserAssetsByDeviceId
|
||||
*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Get all asset of a device that are in the database, ID only.
|
||||
*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} |
|
||||
*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics |
|
||||
*AssetsApi* | [**getMemoryLane**](doc//AssetsApi.md#getmemorylane) | **GET** /assets/memory-lane |
|
||||
*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random |
|
||||
*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback |
|
||||
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | replaceAsset
|
||||
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id
|
||||
*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs |
|
||||
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} |
|
||||
*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets |
|
||||
|
||||
16
mobile/openapi/lib/api/assets_api.dart
generated
16
mobile/openapi/lib/api/assets_api.dart
generated
@@ -16,8 +16,6 @@ class AssetsApi {
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// checkBulkUpload
|
||||
///
|
||||
/// Checks if assets exist by checksums
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
@@ -50,8 +48,6 @@ class AssetsApi {
|
||||
);
|
||||
}
|
||||
|
||||
/// checkBulkUpload
|
||||
///
|
||||
/// Checks if assets exist by checksums
|
||||
///
|
||||
/// Parameters:
|
||||
@@ -72,8 +68,6 @@ class AssetsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// checkExistingAssets
|
||||
///
|
||||
/// Checks if multiple assets exist on the server and returns all existing - used by background backup
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
@@ -106,8 +100,6 @@ class AssetsApi {
|
||||
);
|
||||
}
|
||||
|
||||
/// checkExistingAssets
|
||||
///
|
||||
/// Checks if multiple assets exist on the server and returns all existing - used by background backup
|
||||
///
|
||||
/// Parameters:
|
||||
@@ -223,8 +215,6 @@ class AssetsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// getAllUserAssetsByDeviceId
|
||||
///
|
||||
/// Get all asset of a device that are in the database, ID only.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
@@ -258,8 +248,6 @@ class AssetsApi {
|
||||
);
|
||||
}
|
||||
|
||||
/// getAllUserAssetsByDeviceId
|
||||
///
|
||||
/// Get all asset of a device that are in the database, ID only.
|
||||
///
|
||||
/// Parameters:
|
||||
@@ -576,8 +564,6 @@ class AssetsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// replaceAsset
|
||||
///
|
||||
/// Replace the asset with new file, without changing its id
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
@@ -659,8 +645,6 @@ class AssetsApi {
|
||||
);
|
||||
}
|
||||
|
||||
/// replaceAsset
|
||||
///
|
||||
/// Replace the asset with new file, without changing its id
|
||||
///
|
||||
/// Parameters:
|
||||
|
||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: 'none'
|
||||
version: 1.125.7+182
|
||||
version: 1.125.6+182
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.0 <4.0.0'
|
||||
|
||||
@@ -539,7 +539,7 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
@@ -1424,7 +1424,6 @@
|
||||
},
|
||||
"/assets/bulk-upload-check": {
|
||||
"post": {
|
||||
"description": "Checks if assets exist by checksums",
|
||||
"operationId": "checkBulkUpload",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
@@ -1460,7 +1459,7 @@
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "checkBulkUpload",
|
||||
"summary": "Checks if assets exist by checksums",
|
||||
"tags": [
|
||||
"Assets"
|
||||
]
|
||||
@@ -1468,7 +1467,6 @@
|
||||
},
|
||||
"/assets/device/{deviceId}": {
|
||||
"get": {
|
||||
"description": "Get all asset of a device that are in the database, ID only.",
|
||||
"operationId": "getAllUserAssetsByDeviceId",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1506,7 +1504,7 @@
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "getAllUserAssetsByDeviceId",
|
||||
"summary": "Get all asset of a device that are in the database, ID only.",
|
||||
"tags": [
|
||||
"Assets"
|
||||
]
|
||||
@@ -1514,7 +1512,6 @@
|
||||
},
|
||||
"/assets/exist": {
|
||||
"post": {
|
||||
"description": "Checks if multiple assets exist on the server and returns all existing - used by background backup",
|
||||
"operationId": "checkExistingAssets",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
@@ -1550,7 +1547,7 @@
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "checkExistingAssets",
|
||||
"summary": "Checks if multiple assets exist on the server and returns all existing - used by background backup",
|
||||
"tags": [
|
||||
"Assets"
|
||||
]
|
||||
@@ -1906,7 +1903,6 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"description": "Replace the asset with new file, without changing its id",
|
||||
"operationId": "replaceAsset",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1960,7 +1956,7 @@
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "replaceAsset",
|
||||
"summary": "Replace the asset with new file, without changing its id",
|
||||
"tags": [
|
||||
"Assets"
|
||||
],
|
||||
@@ -7458,7 +7454,7 @@
|
||||
"info": {
|
||||
"title": "Immich",
|
||||
"description": "Immich API",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"contact": {}
|
||||
},
|
||||
"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",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||
"type": "module",
|
||||
"main": "./build/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Immich
|
||||
* 1.125.7
|
||||
* 1.125.6
|
||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||
* See https://www.npmjs.com/package/oazapfts
|
||||
*/
|
||||
@@ -1475,7 +1475,7 @@ export function restoreUserAdmin({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
status: 201;
|
||||
data: UserAdminResponseDto;
|
||||
}>(`/admin/users/${encodeURIComponent(id)}/restore`, {
|
||||
...opts,
|
||||
@@ -1703,7 +1703,7 @@ export function updateAssets({ assetBulkUpdateDto }: {
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* checkBulkUpload
|
||||
* Checks if assets exist by checksums
|
||||
*/
|
||||
export function checkBulkUpload({ assetBulkUploadCheckDto }: {
|
||||
assetBulkUploadCheckDto: AssetBulkUploadCheckDto;
|
||||
@@ -1718,7 +1718,7 @@ export function checkBulkUpload({ assetBulkUploadCheckDto }: {
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* getAllUserAssetsByDeviceId
|
||||
* Get all asset of a device that are in the database, ID only.
|
||||
*/
|
||||
export function getAllUserAssetsByDeviceId({ deviceId }: {
|
||||
deviceId: string;
|
||||
@@ -1731,7 +1731,7 @@ export function getAllUserAssetsByDeviceId({ deviceId }: {
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* checkExistingAssets
|
||||
* Checks if multiple assets exist on the server and returns all existing - used by background backup
|
||||
*/
|
||||
export function checkExistingAssets({ checkExistingAssetsDto }: {
|
||||
checkExistingAssetsDto: CheckExistingAssetsDto;
|
||||
@@ -1839,7 +1839,7 @@ export function downloadAsset({ id, key }: {
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* replaceAsset
|
||||
* Replace the asset with new file, without changing its id
|
||||
*/
|
||||
export function replaceAsset({ id, key, assetMediaReplaceDto }: {
|
||||
id: string;
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@nestjs/bullmq": "^11.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
UploadedFiles,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBody, ApiConsumes, ApiHeader, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger';
|
||||
import { NextFunction, Response } from 'express';
|
||||
import { EndpointLifecycle } from 'src/decorators';
|
||||
import {
|
||||
@@ -94,10 +94,6 @@ export class AssetMediaController {
|
||||
@UseInterceptors(FileUploadInterceptor)
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@EndpointLifecycle({ addedAt: 'v1.106.0' })
|
||||
@ApiOperation({
|
||||
summary: 'replaceAsset',
|
||||
description: 'Replace the asset with new file, without changing its id',
|
||||
})
|
||||
@Authenticated({ sharedLink: true })
|
||||
async replaceAsset(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -145,10 +141,6 @@ export class AssetMediaController {
|
||||
*/
|
||||
@Post('exist')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({
|
||||
summary: 'checkExistingAssets',
|
||||
description: 'Checks if multiple assets exist on the server and returns all existing - used by background backup',
|
||||
})
|
||||
@Authenticated()
|
||||
checkExistingAssets(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -162,10 +154,6 @@ export class AssetMediaController {
|
||||
*/
|
||||
@Post('bulk-upload-check')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({
|
||||
summary: 'checkBulkUpload',
|
||||
description: 'Checks if assets exist by checksums',
|
||||
})
|
||||
@Authenticated()
|
||||
checkBulkUpload(
|
||||
@Auth() auth: AuthDto,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { EndpointLifecycle } from 'src/decorators';
|
||||
import { AssetResponseDto, MemoryLaneResponseDto } from 'src/dtos/asset-response.dto';
|
||||
import {
|
||||
@@ -41,10 +41,6 @@ export class AssetController {
|
||||
* Get all asset of a device that are in the database, ID only.
|
||||
*/
|
||||
@Get('/device/:deviceId')
|
||||
@ApiOperation({
|
||||
summary: 'getAllUserAssetsByDeviceId',
|
||||
description: 'Get all asset of a device that are in the database, ID only.',
|
||||
})
|
||||
@Authenticated()
|
||||
getAllUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) {
|
||||
return this.service.getUserAssetsByDeviceId(auth, deviceId);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto';
|
||||
@@ -75,7 +75,6 @@ export class UserAdminController {
|
||||
|
||||
@Post(':id/restore')
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
||||
return this.service.restore(auth, id);
|
||||
}
|
||||
|
||||
28
server/src/db.d.ts
vendored
28
server/src/db.d.ts
vendored
@@ -3,21 +3,16 @@
|
||||
* Please do not edit it manually.
|
||||
*/
|
||||
|
||||
import type { ColumnType } from "kysely";
|
||||
import type { ColumnType } from 'kysely';
|
||||
|
||||
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[]
|
||||
? U[]
|
||||
: ArrayTypeImpl<T>;
|
||||
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;
|
||||
|
||||
export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
? ColumnType<S[], I[], U[]>
|
||||
: T[];
|
||||
export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S[], I[], U[]> : T[];
|
||||
|
||||
export type AssetsStatusEnum = "active" | "deleted" | "trashed";
|
||||
export type AssetsStatusEnum = 'active' | 'deleted' | 'trashed';
|
||||
|
||||
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
? ColumnType<S, I | undefined, U>
|
||||
: ColumnType<T, T | undefined, T>;
|
||||
export type Generated<T> =
|
||||
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
|
||||
|
||||
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;
|
||||
|
||||
@@ -33,7 +28,7 @@ export type JsonPrimitive = boolean | number | string | null;
|
||||
|
||||
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
|
||||
|
||||
export type Sourcetype = "exif" | "machine-learning";
|
||||
export type Sourcetype = 'exif' | 'machine-learning';
|
||||
|
||||
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||
|
||||
@@ -154,6 +149,12 @@ export interface AssetStack {
|
||||
primaryAssetId: string;
|
||||
}
|
||||
|
||||
export interface AssetUser {
|
||||
assetId: string;
|
||||
createdAt: Timestamp;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface Audit {
|
||||
action: string;
|
||||
createdAt: Generated<Timestamp>;
|
||||
@@ -413,6 +414,7 @@ export interface DB {
|
||||
asset_files: AssetFiles;
|
||||
asset_job_status: AssetJobStatus;
|
||||
asset_stack: AssetStack;
|
||||
asset_user: AssetUser;
|
||||
assets: Assets;
|
||||
audit: Audit;
|
||||
exif: Exif;
|
||||
@@ -438,6 +440,6 @@ export interface DB {
|
||||
tags_closure: TagsClosure;
|
||||
user_metadata: UserMetadata;
|
||||
users: Users;
|
||||
"vectors.pg_vector_index_stat": VectorsPgVectorIndexStat;
|
||||
'vectors.pg_vector_index_stat': VectorsPgVectorIndexStat;
|
||||
version_history: VersionHistory;
|
||||
}
|
||||
|
||||
22
server/src/entities/asset-user.entity.ts
Normal file
22
server/src/entities/asset-user.entity.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { Column, Entity, Index, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
|
||||
@Entity('asset_user')
|
||||
@Index('IDX_assetId_userId', ['assetId', 'userId'])
|
||||
export class AssetUserEntity {
|
||||
@PrimaryColumn()
|
||||
assetId!: string;
|
||||
|
||||
@ManyToOne(() => AssetEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||
asset!: AssetEntity;
|
||||
|
||||
@PrimaryColumn()
|
||||
userId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||
user!: UserEntity;
|
||||
|
||||
@Column()
|
||||
createdAt!: Date;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { APIKeyEntity } from 'src/entities/api-key.entity';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||
import { AssetUserEntity } from 'src/entities/asset-user.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AuditEntity } from 'src/entities/audit.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
@@ -34,6 +35,7 @@ export const entities = [
|
||||
AssetEntity,
|
||||
AssetFaceEntity,
|
||||
AssetFileEntity,
|
||||
AssetUserEntity,
|
||||
AssetJobStatusEntity,
|
||||
AuditEntity,
|
||||
ExifEntity,
|
||||
|
||||
@@ -36,7 +36,6 @@ export interface IUserRepository {
|
||||
getUserStats(): Promise<UserStatsQueryResponse[]>;
|
||||
create(user: Insertable<Users>): Promise<UserEntity>;
|
||||
update(id: string, user: Updateable<Users>): Promise<UserEntity>;
|
||||
restore(id: string): Promise<UserEntity>;
|
||||
upsertMetadata<T extends keyof UserMetadata>(id: string, item: { key: T; value: UserMetadata[T] }): Promise<void>;
|
||||
deleteMetadata<T extends keyof UserMetadata>(id: string, key: T): Promise<void>;
|
||||
delete(user: UserEntity, hard?: boolean): Promise<UserEntity>;
|
||||
|
||||
20
server/src/migrations/1738099775096-AddAssetUserTable.ts
Normal file
20
server/src/migrations/1738099775096-AddAssetUserTable.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddAssetUserTable1738099775096 implements MigrationInterface {
|
||||
name = 'AddAssetUserTable1738099775096'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "asset_user" ("assetId" uuid NOT NULL, "userId" uuid NOT NULL, "createdAt" TIMESTAMP NOT NULL, CONSTRAINT "PK_f3d7f17ab93d60e007282726058" PRIMARY KEY ("assetId", "userId"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_assetId_userId" ON "asset_user" ("assetId", "userId") `);
|
||||
await queryRunner.query(`ALTER TABLE "asset_user" ADD CONSTRAINT "FK_07c8478e0936e78b553aaeceedb" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_user" ADD CONSTRAINT "FK_85e2ef24493bdf649dfdfb769a2" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "asset_user" DROP CONSTRAINT "FK_85e2ef24493bdf649dfdfb769a2"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_user" DROP CONSTRAINT "FK_07c8478e0936e78b553aaeceedb"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_assetId_userId"`);
|
||||
await queryRunner.query(`DROP TABLE "asset_user"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -81,7 +81,24 @@ export class AssetRepository implements IAssetRepository {
|
||||
}
|
||||
|
||||
create(asset: Insertable<Assets>): Promise<AssetEntity> {
|
||||
return this.db.insertInto('assets').values(asset).returningAll().executeTakeFirst() as any as Promise<AssetEntity>;
|
||||
return this.db.transaction().execute(async (tx) => {
|
||||
const newAsset = (await tx
|
||||
.insertInto('assets')
|
||||
.values(asset)
|
||||
.returningAll()
|
||||
.executeTakeFirst()) as any as AssetEntity;
|
||||
|
||||
await tx
|
||||
.insertInto('asset_user')
|
||||
.values({
|
||||
assetId: newAsset.id,
|
||||
userId: newAsset.ownerId,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
.execute();
|
||||
|
||||
return newAsset;
|
||||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DB, UserMetadata as DbUserMetadata, Users } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { UserMetadata } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity, withMetadata } from 'src/entities/user.entity';
|
||||
import { UserStatus } from 'src/enum';
|
||||
import {
|
||||
IUserRepository,
|
||||
UserFindOptions,
|
||||
@@ -141,16 +140,6 @@ export class UserRepository implements IUserRepository {
|
||||
.executeTakeFirst() as unknown as Promise<UserEntity>;
|
||||
}
|
||||
|
||||
restore(id: string): Promise<UserEntity> {
|
||||
return this.db
|
||||
.updateTable('users')
|
||||
.set({ status: UserStatus.ACTIVE, deletedAt: null })
|
||||
.where('users.id', '=', asUuid(id))
|
||||
.returning(columns)
|
||||
.returning(withMetadata)
|
||||
.executeTakeFirst() as unknown as Promise<UserEntity>;
|
||||
}
|
||||
|
||||
async upsertMetadata<T extends keyof UserMetadata>(id: string, { key, value }: { key: T; value: UserMetadata[T] }) {
|
||||
await this.db
|
||||
.insertInto('user_metadata')
|
||||
|
||||
@@ -73,7 +73,6 @@ const validImages = [
|
||||
'.heic',
|
||||
'.heif',
|
||||
'.iiq',
|
||||
'.jp2',
|
||||
'.jpeg',
|
||||
'.jpg',
|
||||
'.jxl',
|
||||
|
||||
@@ -173,9 +173,9 @@ describe(UserAdminService.name, () => {
|
||||
|
||||
it('should restore an user', async () => {
|
||||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
userMock.restore.mockResolvedValue(userStub.user1);
|
||||
userMock.update.mockResolvedValue(userStub.user1);
|
||||
await expect(sut.restore(authStub.admin, userStub.user1.id)).resolves.toEqual(mapUserAdmin(userStub.user1));
|
||||
expect(userMock.restore).toHaveBeenCalledWith(userStub.user1.id);
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { status: UserStatus.ACTIVE, deletedAt: null });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -102,7 +102,7 @@ export class UserAdminService extends BaseService {
|
||||
async restore(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {
|
||||
await this.findOrFail(id, { withDeleted: true });
|
||||
await this.albumRepository.restoreAll(id);
|
||||
const user = await this.userRepository.restore(id);
|
||||
const user = await this.userRepository.update(id, { deletedAt: null, status: UserStatus.ACTIVE });
|
||||
return mapUserAdmin(user);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ describe('mimeTypes', () => {
|
||||
{ mimetype: 'image/heif', extension: '.heif' },
|
||||
{ mimetype: 'image/hif', extension: '.hif' },
|
||||
{ mimetype: 'image/iiq', extension: '.iiq' },
|
||||
{ mimetype: 'image/jp2', extension: '.jp2' },
|
||||
{ mimetype: 'image/jpeg', extension: '.jpe' },
|
||||
{ mimetype: 'image/jpeg', extension: '.jpeg' },
|
||||
{ mimetype: 'image/jpeg', extension: '.jpg' },
|
||||
|
||||
@@ -43,7 +43,6 @@ const image: Record<string, string[]> = {
|
||||
'.heif': ['image/heif'],
|
||||
'.hif': ['image/hif'],
|
||||
'.insp': ['image/jpeg'],
|
||||
'.jp2': ['image/jp2'],
|
||||
'.jpe': ['image/jpeg'],
|
||||
'.jpeg': ['image/jpeg'],
|
||||
'.jpg': ['image/jpeg'],
|
||||
|
||||
@@ -13,7 +13,6 @@ export const newUserRepositoryMock = (): Mocked<IUserRepository> => {
|
||||
create: vitest.fn(),
|
||||
update: vitest.fn(),
|
||||
delete: vitest.fn(),
|
||||
restore: vitest.fn(),
|
||||
getDeletedUsers: vitest.fn(),
|
||||
hasAdmin: vitest.fn(),
|
||||
updateUsage: vitest.fn(),
|
||||
|
||||
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-web",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
@@ -77,7 +77,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.125.7",
|
||||
"version": "1.125.6",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"scripts": {
|
||||
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
locale,
|
||||
type AlbumViewSettings,
|
||||
} from '$lib/stores/preferences.store';
|
||||
import { userInteraction } from '$lib/stores/user.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -294,15 +293,6 @@
|
||||
sharedAlbums[sharedAlbums.findIndex(({ id }) => id === album.id)] = album;
|
||||
};
|
||||
|
||||
const updateRecentAlbumInfo = (album: AlbumResponseDto) => {
|
||||
for (const cachedAlbum of userInteraction.recentAlbums || []) {
|
||||
if (cachedAlbum.id === album.id) {
|
||||
Object.assign(cachedAlbum, { ...cachedAlbum, ...album });
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const successEditAlbumInfo = (album: AlbumResponseDto) => {
|
||||
albumToEdit = null;
|
||||
|
||||
@@ -318,7 +308,6 @@
|
||||
});
|
||||
|
||||
updateAlbumInfo(album);
|
||||
updateRecentAlbumInfo(album);
|
||||
};
|
||||
|
||||
const handleAddUsers = async (albumUsers: AlbumUserAddDto[]) => {
|
||||
|
||||
Reference in New Issue
Block a user