mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor!: remove replace asset (#27022)
This commit is contained in:
@@ -77,18 +77,4 @@ test.describe('Photo Viewer', () => {
|
|||||||
});
|
});
|
||||||
expect(tagAtCenter).toBe('IMG');
|
expect(tagAtCenter).toBe('IMG');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reloads photo when checksum changes', async ({ page }) => {
|
|
||||||
await page.goto(`/photos/${asset.id}`);
|
|
||||||
|
|
||||||
const preview = page.getByTestId('preview').filter({ visible: true });
|
|
||||||
await expect(preview).toHaveAttribute('src', /.+/);
|
|
||||||
const initialSrc = await preview.getAttribute('src');
|
|
||||||
|
|
||||||
const websocketEvent = utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id });
|
|
||||||
await utils.replaceAsset(admin.accessToken, asset.id);
|
|
||||||
await websocketEvent;
|
|
||||||
|
|
||||||
await expect(preview).not.toHaveAttribute('src', initialSrc!);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -375,40 +375,6 @@ export const utils = {
|
|||||||
return body as AssetMediaResponseDto;
|
return body as AssetMediaResponseDto;
|
||||||
},
|
},
|
||||||
|
|
||||||
replaceAsset: async (
|
|
||||||
accessToken: string,
|
|
||||||
assetId: string,
|
|
||||||
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData'>> & { assetData?: FileData },
|
|
||||||
) => {
|
|
||||||
const _dto = {
|
|
||||||
deviceAssetId: 'test-1',
|
|
||||||
deviceId: 'test',
|
|
||||||
fileCreatedAt: new Date().toISOString(),
|
|
||||||
fileModifiedAt: new Date().toISOString(),
|
|
||||||
...dto,
|
|
||||||
};
|
|
||||||
|
|
||||||
const assetData = dto?.assetData?.bytes || makeRandomImage();
|
|
||||||
const filename = dto?.assetData?.filename || 'example.png';
|
|
||||||
|
|
||||||
if (dto?.assetData?.bytes) {
|
|
||||||
console.log(`Uploading ${filename}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const builder = request(app)
|
|
||||||
.put(`/assets/${assetId}/original`)
|
|
||||||
.attach('assetData', assetData, filename)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`);
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(_dto)) {
|
|
||||||
void builder.field(key, String(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { body } = await builder;
|
|
||||||
|
|
||||||
return body as AssetMediaResponseDto;
|
|
||||||
},
|
|
||||||
|
|
||||||
createImageFile: (path: string) => {
|
createImageFile: (path: string) => {
|
||||||
if (!existsSync(dirname(path))) {
|
if (!existsSync(dirname(path))) {
|
||||||
mkdirSync(dirname(path), { recursive: true });
|
mkdirSync(dirname(path), { recursive: true });
|
||||||
|
|||||||
Generated
-2
@@ -113,7 +113,6 @@ Class | Method | HTTP request | Description
|
|||||||
*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | Get random assets
|
*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | Get random assets
|
||||||
*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | Play asset video
|
*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | Play asset video
|
||||||
*AssetsApi* | [**removeAssetEdits**](doc//AssetsApi.md#removeassetedits) | **DELETE** /assets/{id}/edits | Remove edits from an existing asset
|
*AssetsApi* | [**removeAssetEdits**](doc//AssetsApi.md#removeassetedits) | **DELETE** /assets/{id}/edits | Remove edits from an existing asset
|
||||||
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset
|
|
||||||
*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | Run an asset job
|
*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | Run an asset job
|
||||||
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | Update an asset
|
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | Update an asset
|
||||||
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | Update asset metadata
|
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | Update asset metadata
|
||||||
@@ -149,7 +148,6 @@ Class | Method | HTTP request | Description
|
|||||||
*DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user
|
*DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user
|
||||||
*DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status
|
*DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status
|
||||||
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets
|
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets
|
||||||
*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset
|
|
||||||
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
|
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
|
||||||
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
|
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
|
||||||
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
|
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
|
||||||
|
|||||||
Generated
-148
@@ -1113,154 +1113,6 @@ class AssetsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace asset
|
|
||||||
///
|
|
||||||
/// Replace the asset with new file, without changing its id.
|
|
||||||
///
|
|
||||||
/// Note: This method returns the HTTP [Response].
|
|
||||||
///
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [MultipartFile] assetData (required):
|
|
||||||
/// Asset file data
|
|
||||||
///
|
|
||||||
/// * [String] deviceAssetId (required):
|
|
||||||
/// Device asset ID
|
|
||||||
///
|
|
||||||
/// * [String] deviceId (required):
|
|
||||||
/// Device ID
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileCreatedAt (required):
|
|
||||||
/// File creation date
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileModifiedAt (required):
|
|
||||||
/// File modification date
|
|
||||||
///
|
|
||||||
/// * [String] key:
|
|
||||||
///
|
|
||||||
/// * [String] slug:
|
|
||||||
///
|
|
||||||
/// * [String] duration:
|
|
||||||
/// Duration (for videos)
|
|
||||||
///
|
|
||||||
/// * [String] filename:
|
|
||||||
/// Filename
|
|
||||||
Future<Response> replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final apiPath = r'/assets/{id}/original'
|
|
||||||
.replaceAll('{id}', id);
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
if (key != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'key', key));
|
|
||||||
}
|
|
||||||
if (slug != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'slug', slug));
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentTypes = <String>['multipart/form-data'];
|
|
||||||
|
|
||||||
bool hasFields = false;
|
|
||||||
final mp = MultipartRequest('PUT', Uri.parse(apiPath));
|
|
||||||
if (assetData != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'assetData'] = assetData.field;
|
|
||||||
mp.files.add(assetData);
|
|
||||||
}
|
|
||||||
if (deviceAssetId != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId);
|
|
||||||
}
|
|
||||||
if (deviceId != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'deviceId'] = parameterToString(deviceId);
|
|
||||||
}
|
|
||||||
if (duration != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'duration'] = parameterToString(duration);
|
|
||||||
}
|
|
||||||
if (fileCreatedAt != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'fileCreatedAt'] = parameterToString(fileCreatedAt);
|
|
||||||
}
|
|
||||||
if (fileModifiedAt != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt);
|
|
||||||
}
|
|
||||||
if (filename != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'filename'] = parameterToString(filename);
|
|
||||||
}
|
|
||||||
if (hasFields) {
|
|
||||||
postBody = mp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
apiPath,
|
|
||||||
'PUT',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace asset
|
|
||||||
///
|
|
||||||
/// Replace the asset with new file, without changing its id.
|
|
||||||
///
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [MultipartFile] assetData (required):
|
|
||||||
/// Asset file data
|
|
||||||
///
|
|
||||||
/// * [String] deviceAssetId (required):
|
|
||||||
/// Device asset ID
|
|
||||||
///
|
|
||||||
/// * [String] deviceId (required):
|
|
||||||
/// Device ID
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileCreatedAt (required):
|
|
||||||
/// File creation date
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileModifiedAt (required):
|
|
||||||
/// File modification date
|
|
||||||
///
|
|
||||||
/// * [String] key:
|
|
||||||
///
|
|
||||||
/// * [String] slug:
|
|
||||||
///
|
|
||||||
/// * [String] duration:
|
|
||||||
/// Duration (for videos)
|
|
||||||
///
|
|
||||||
/// * [String] filename:
|
|
||||||
/// Filename
|
|
||||||
Future<AssetMediaResponseDto?> replaceAsset(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
|
||||||
final response = await replaceAssetWithHttpInfo(id, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, duration: duration, filename: filename, );
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
|
||||||
}
|
|
||||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
|
||||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
|
||||||
// FormatException when trying to decode an empty string.
|
|
||||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
|
||||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetMediaResponseDto',) as AssetMediaResponseDto;
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run an asset job
|
/// Run an asset job
|
||||||
///
|
///
|
||||||
/// Run a specific job on a set of assets.
|
/// Run a specific job on a set of assets.
|
||||||
|
|||||||
-148
@@ -363,154 +363,6 @@ class DeprecatedApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace asset
|
|
||||||
///
|
|
||||||
/// Replace the asset with new file, without changing its id.
|
|
||||||
///
|
|
||||||
/// Note: This method returns the HTTP [Response].
|
|
||||||
///
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [MultipartFile] assetData (required):
|
|
||||||
/// Asset file data
|
|
||||||
///
|
|
||||||
/// * [String] deviceAssetId (required):
|
|
||||||
/// Device asset ID
|
|
||||||
///
|
|
||||||
/// * [String] deviceId (required):
|
|
||||||
/// Device ID
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileCreatedAt (required):
|
|
||||||
/// File creation date
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileModifiedAt (required):
|
|
||||||
/// File modification date
|
|
||||||
///
|
|
||||||
/// * [String] key:
|
|
||||||
///
|
|
||||||
/// * [String] slug:
|
|
||||||
///
|
|
||||||
/// * [String] duration:
|
|
||||||
/// Duration (for videos)
|
|
||||||
///
|
|
||||||
/// * [String] filename:
|
|
||||||
/// Filename
|
|
||||||
Future<Response> replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final apiPath = r'/assets/{id}/original'
|
|
||||||
.replaceAll('{id}', id);
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
if (key != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'key', key));
|
|
||||||
}
|
|
||||||
if (slug != null) {
|
|
||||||
queryParams.addAll(_queryParams('', 'slug', slug));
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentTypes = <String>['multipart/form-data'];
|
|
||||||
|
|
||||||
bool hasFields = false;
|
|
||||||
final mp = MultipartRequest('PUT', Uri.parse(apiPath));
|
|
||||||
if (assetData != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'assetData'] = assetData.field;
|
|
||||||
mp.files.add(assetData);
|
|
||||||
}
|
|
||||||
if (deviceAssetId != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId);
|
|
||||||
}
|
|
||||||
if (deviceId != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'deviceId'] = parameterToString(deviceId);
|
|
||||||
}
|
|
||||||
if (duration != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'duration'] = parameterToString(duration);
|
|
||||||
}
|
|
||||||
if (fileCreatedAt != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'fileCreatedAt'] = parameterToString(fileCreatedAt);
|
|
||||||
}
|
|
||||||
if (fileModifiedAt != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt);
|
|
||||||
}
|
|
||||||
if (filename != null) {
|
|
||||||
hasFields = true;
|
|
||||||
mp.fields[r'filename'] = parameterToString(filename);
|
|
||||||
}
|
|
||||||
if (hasFields) {
|
|
||||||
postBody = mp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
apiPath,
|
|
||||||
'PUT',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace asset
|
|
||||||
///
|
|
||||||
/// Replace the asset with new file, without changing its id.
|
|
||||||
///
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [MultipartFile] assetData (required):
|
|
||||||
/// Asset file data
|
|
||||||
///
|
|
||||||
/// * [String] deviceAssetId (required):
|
|
||||||
/// Device asset ID
|
|
||||||
///
|
|
||||||
/// * [String] deviceId (required):
|
|
||||||
/// Device ID
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileCreatedAt (required):
|
|
||||||
/// File creation date
|
|
||||||
///
|
|
||||||
/// * [DateTime] fileModifiedAt (required):
|
|
||||||
/// File modification date
|
|
||||||
///
|
|
||||||
/// * [String] key:
|
|
||||||
///
|
|
||||||
/// * [String] slug:
|
|
||||||
///
|
|
||||||
/// * [String] duration:
|
|
||||||
/// Duration (for videos)
|
|
||||||
///
|
|
||||||
/// * [String] filename:
|
|
||||||
/// Filename
|
|
||||||
Future<AssetMediaResponseDto?> replaceAsset(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async {
|
|
||||||
final response = await replaceAssetWithHttpInfo(id, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, duration: duration, filename: filename, );
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
|
||||||
}
|
|
||||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
|
||||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
|
||||||
// FormatException when trying to decode an empty string.
|
|
||||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
|
||||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetMediaResponseDto',) as AssetMediaResponseDto;
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run jobs
|
/// Run jobs
|
||||||
///
|
///
|
||||||
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
|
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
|
||||||
|
|||||||
Generated
-3
@@ -41,7 +41,6 @@ class Permission {
|
|||||||
static const assetPeriodView = Permission._(r'asset.view');
|
static const assetPeriodView = Permission._(r'asset.view');
|
||||||
static const assetPeriodDownload = Permission._(r'asset.download');
|
static const assetPeriodDownload = Permission._(r'asset.download');
|
||||||
static const assetPeriodUpload = Permission._(r'asset.upload');
|
static const assetPeriodUpload = Permission._(r'asset.upload');
|
||||||
static const assetPeriodReplace = Permission._(r'asset.replace');
|
|
||||||
static const assetPeriodCopy = Permission._(r'asset.copy');
|
static const assetPeriodCopy = Permission._(r'asset.copy');
|
||||||
static const assetPeriodDerive = Permission._(r'asset.derive');
|
static const assetPeriodDerive = Permission._(r'asset.derive');
|
||||||
static const assetPeriodEditPeriodGet = Permission._(r'asset.edit.get');
|
static const assetPeriodEditPeriodGet = Permission._(r'asset.edit.get');
|
||||||
@@ -200,7 +199,6 @@ class Permission {
|
|||||||
assetPeriodView,
|
assetPeriodView,
|
||||||
assetPeriodDownload,
|
assetPeriodDownload,
|
||||||
assetPeriodUpload,
|
assetPeriodUpload,
|
||||||
assetPeriodReplace,
|
|
||||||
assetPeriodCopy,
|
assetPeriodCopy,
|
||||||
assetPeriodDerive,
|
assetPeriodDerive,
|
||||||
assetPeriodEditPeriodGet,
|
assetPeriodEditPeriodGet,
|
||||||
@@ -394,7 +392,6 @@ class PermissionTypeTransformer {
|
|||||||
case r'asset.view': return Permission.assetPeriodView;
|
case r'asset.view': return Permission.assetPeriodView;
|
||||||
case r'asset.download': return Permission.assetPeriodDownload;
|
case r'asset.download': return Permission.assetPeriodDownload;
|
||||||
case r'asset.upload': return Permission.assetPeriodUpload;
|
case r'asset.upload': return Permission.assetPeriodUpload;
|
||||||
case r'asset.replace': return Permission.assetPeriodReplace;
|
|
||||||
case r'asset.copy': return Permission.assetPeriodCopy;
|
case r'asset.copy': return Permission.assetPeriodCopy;
|
||||||
case r'asset.derive': return Permission.assetPeriodDerive;
|
case r'asset.derive': return Permission.assetPeriodDerive;
|
||||||
case r'asset.edit.get': return Permission.assetPeriodEditPeriodGet;
|
case r'asset.edit.get': return Permission.assetPeriodEditPeriodGet;
|
||||||
|
|||||||
@@ -4271,90 +4271,6 @@
|
|||||||
],
|
],
|
||||||
"x-immich-permission": "asset.download",
|
"x-immich-permission": "asset.download",
|
||||||
"x-immich-state": "Stable"
|
"x-immich-state": "Stable"
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"deprecated": true,
|
|
||||||
"description": "Replace the asset with new file, without changing its id.",
|
|
||||||
"operationId": "replaceAsset",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"format": "uuid",
|
|
||||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "key",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "slug",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"multipart/form-data": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/AssetMediaReplaceDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/AssetMediaResponseDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": "Asset replaced successfully"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"bearer": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cookie": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"api_key": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"summary": "Replace asset",
|
|
||||||
"tags": [
|
|
||||||
"Assets",
|
|
||||||
"Deprecated"
|
|
||||||
],
|
|
||||||
"x-immich-history": [
|
|
||||||
{
|
|
||||||
"version": "v1",
|
|
||||||
"state": "Added"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "v1",
|
|
||||||
"state": "Deprecated",
|
|
||||||
"replacementId": "copyAsset"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"x-immich-permission": "asset.replace",
|
|
||||||
"x-immich-state": "Deprecated"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/assets/{id}/thumbnail": {
|
"/assets/{id}/thumbnail": {
|
||||||
@@ -16818,53 +16734,6 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"AssetMediaReplaceDto": {
|
|
||||||
"properties": {
|
|
||||||
"assetData": {
|
|
||||||
"description": "Asset file data",
|
|
||||||
"format": "binary",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"deviceAssetId": {
|
|
||||||
"description": "Device asset ID",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"deviceId": {
|
|
||||||
"description": "Device ID",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"duration": {
|
|
||||||
"description": "Duration (for videos)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"fileCreatedAt": {
|
|
||||||
"description": "File creation date",
|
|
||||||
"example": "2024-01-01T00:00:00.000Z",
|
|
||||||
"format": "date-time",
|
|
||||||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"fileModifiedAt": {
|
|
||||||
"description": "File modification date",
|
|
||||||
"example": "2024-01-01T00:00:00.000Z",
|
|
||||||
"format": "date-time",
|
|
||||||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"filename": {
|
|
||||||
"description": "Filename",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"assetData",
|
|
||||||
"deviceAssetId",
|
|
||||||
"deviceId",
|
|
||||||
"fileCreatedAt",
|
|
||||||
"fileModifiedAt"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"AssetMediaResponseDto": {
|
"AssetMediaResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -20023,7 +19892,6 @@
|
|||||||
"asset.view",
|
"asset.view",
|
||||||
"asset.download",
|
"asset.download",
|
||||||
"asset.upload",
|
"asset.upload",
|
||||||
"asset.replace",
|
|
||||||
"asset.copy",
|
"asset.copy",
|
||||||
"asset.derive",
|
"asset.derive",
|
||||||
"asset.edit.get",
|
"asset.edit.get",
|
||||||
|
|||||||
@@ -1010,22 +1010,6 @@ export type AssetOcrResponseDto = {
|
|||||||
/** Normalized y coordinate of box corner 4 (0-1) */
|
/** Normalized y coordinate of box corner 4 (0-1) */
|
||||||
y4: number;
|
y4: number;
|
||||||
};
|
};
|
||||||
export type AssetMediaReplaceDto = {
|
|
||||||
/** Asset file data */
|
|
||||||
assetData: Blob;
|
|
||||||
/** Device asset ID */
|
|
||||||
deviceAssetId: string;
|
|
||||||
/** Device ID */
|
|
||||||
deviceId: string;
|
|
||||||
/** Duration (for videos) */
|
|
||||||
duration?: string;
|
|
||||||
/** File creation date */
|
|
||||||
fileCreatedAt: string;
|
|
||||||
/** File modification date */
|
|
||||||
fileModifiedAt: string;
|
|
||||||
/** Filename */
|
|
||||||
filename?: string;
|
|
||||||
};
|
|
||||||
export type SignUpDto = {
|
export type SignUpDto = {
|
||||||
/** User email */
|
/** User email */
|
||||||
email: string;
|
email: string;
|
||||||
@@ -4272,27 +4256,6 @@ export function downloadAsset({ edited, id, key, slug }: {
|
|||||||
...opts
|
...opts
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Replace asset
|
|
||||||
*/
|
|
||||||
export function replaceAsset({ id, key, slug, assetMediaReplaceDto }: {
|
|
||||||
id: string;
|
|
||||||
key?: string;
|
|
||||||
slug?: string;
|
|
||||||
assetMediaReplaceDto: AssetMediaReplaceDto;
|
|
||||||
}, opts?: Oazapfts.RequestOpts) {
|
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
|
||||||
status: 200;
|
|
||||||
data: AssetMediaResponseDto;
|
|
||||||
}>(`/assets/${encodeURIComponent(id)}/original${QS.query(QS.explode({
|
|
||||||
key,
|
|
||||||
slug
|
|
||||||
}))}`, oazapfts.multipart({
|
|
||||||
...opts,
|
|
||||||
method: "PUT",
|
|
||||||
body: assetMediaReplaceDto
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* View asset thumbnail
|
* View asset thumbnail
|
||||||
*/
|
*/
|
||||||
@@ -6932,7 +6895,6 @@ export enum Permission {
|
|||||||
AssetView = "asset.view",
|
AssetView = "asset.view",
|
||||||
AssetDownload = "asset.download",
|
AssetDownload = "asset.download",
|
||||||
AssetUpload = "asset.upload",
|
AssetUpload = "asset.upload",
|
||||||
AssetReplace = "asset.replace",
|
|
||||||
AssetCopy = "asset.copy",
|
AssetCopy = "asset.copy",
|
||||||
AssetDerive = "asset.derive",
|
AssetDerive = "asset.derive",
|
||||||
AssetEditGet = "asset.edit.get",
|
AssetEditGet = "asset.edit.get",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
ParseFilePipe,
|
ParseFilePipe,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
|
||||||
Query,
|
Query,
|
||||||
Req,
|
Req,
|
||||||
Res,
|
Res,
|
||||||
@@ -28,10 +27,8 @@ import {
|
|||||||
AssetBulkUploadCheckDto,
|
AssetBulkUploadCheckDto,
|
||||||
AssetMediaCreateDto,
|
AssetMediaCreateDto,
|
||||||
AssetMediaOptionsDto,
|
AssetMediaOptionsDto,
|
||||||
AssetMediaReplaceDto,
|
|
||||||
AssetMediaSize,
|
AssetMediaSize,
|
||||||
CheckExistingAssetsDto,
|
CheckExistingAssetsDto,
|
||||||
UploadFieldName,
|
|
||||||
} from 'src/dtos/asset-media.dto';
|
} from 'src/dtos/asset-media.dto';
|
||||||
import { AssetDownloadOriginalDto } from 'src/dtos/asset.dto';
|
import { AssetDownloadOriginalDto } from 'src/dtos/asset.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
@@ -112,36 +109,6 @@ export class AssetMediaController {
|
|||||||
await sendFile(res, next, () => this.service.downloadOriginal(auth, id, dto), this.logger);
|
await sendFile(res, next, () => this.service.downloadOriginal(auth, id, dto), this.logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id/original')
|
|
||||||
@UseInterceptors(FileUploadInterceptor)
|
|
||||||
@ApiConsumes('multipart/form-data')
|
|
||||||
@ApiResponse({
|
|
||||||
status: 200,
|
|
||||||
description: 'Asset replaced successfully',
|
|
||||||
type: AssetMediaResponseDto,
|
|
||||||
})
|
|
||||||
@Endpoint({
|
|
||||||
summary: 'Replace asset',
|
|
||||||
description: 'Replace the asset with new file, without changing its id.',
|
|
||||||
history: new HistoryBuilder().added('v1').deprecated('v1', { replacementId: 'copyAsset' }),
|
|
||||||
})
|
|
||||||
@Authenticated({ permission: Permission.AssetReplace, sharedLink: true })
|
|
||||||
async replaceAsset(
|
|
||||||
@Auth() auth: AuthDto,
|
|
||||||
@Param() { id }: UUIDParamDto,
|
|
||||||
@UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator([UploadFieldName.ASSET_DATA])] }))
|
|
||||||
files: UploadFiles,
|
|
||||||
@Body() dto: AssetMediaReplaceDto,
|
|
||||||
@Res({ passthrough: true }) res: Response,
|
|
||||||
): Promise<AssetMediaResponseDto> {
|
|
||||||
const { file } = getFiles(files);
|
|
||||||
const responseDto = await this.service.replaceAsset(auth, id, dto, file);
|
|
||||||
if (responseDto.status === AssetMediaStatus.DUPLICATE) {
|
|
||||||
res.status(HttpStatus.OK);
|
|
||||||
}
|
|
||||||
return responseDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(':id/thumbnail')
|
@Get(':id/thumbnail')
|
||||||
@FileResponse()
|
@FileResponse()
|
||||||
@Authenticated({ permission: Permission.AssetView, sharedLink: true })
|
@Authenticated({ permission: Permission.AssetView, sharedLink: true })
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ const AssetMediaCreateSchema = AssetMediaBaseSchema.extend({
|
|||||||
.meta({ type: 'string', format: 'binary' }),
|
.meta({ type: 'string', format: 'binary' }),
|
||||||
}).meta({ id: 'AssetMediaCreateDto' });
|
}).meta({ id: 'AssetMediaCreateDto' });
|
||||||
|
|
||||||
const AssetMediaReplaceSchema = AssetMediaBaseSchema.meta({ id: 'AssetMediaReplaceDto' });
|
|
||||||
|
|
||||||
const AssetBulkUploadCheckItemSchema = z
|
const AssetBulkUploadCheckItemSchema = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string().describe('Asset ID'),
|
id: z.string().describe('Asset ID'),
|
||||||
@@ -77,6 +75,5 @@ const CheckExistingAssetsSchema = z
|
|||||||
|
|
||||||
export class AssetMediaOptionsDto extends createZodDto(AssetMediaOptionsSchema) {}
|
export class AssetMediaOptionsDto extends createZodDto(AssetMediaOptionsSchema) {}
|
||||||
export class AssetMediaCreateDto extends createZodDto(AssetMediaCreateSchema) {}
|
export class AssetMediaCreateDto extends createZodDto(AssetMediaCreateSchema) {}
|
||||||
export class AssetMediaReplaceDto extends createZodDto(AssetMediaReplaceSchema) {}
|
|
||||||
export class AssetBulkUploadCheckDto extends createZodDto(AssetBulkUploadCheckSchema) {}
|
export class AssetBulkUploadCheckDto extends createZodDto(AssetBulkUploadCheckSchema) {}
|
||||||
export class CheckExistingAssetsDto extends createZodDto(CheckExistingAssetsSchema) {}
|
export class CheckExistingAssetsDto extends createZodDto(CheckExistingAssetsSchema) {}
|
||||||
|
|||||||
@@ -146,7 +146,6 @@ export enum Permission {
|
|||||||
AssetView = 'asset.view',
|
AssetView = 'asset.view',
|
||||||
AssetDownload = 'asset.download',
|
AssetDownload = 'asset.download',
|
||||||
AssetUpload = 'asset.upload',
|
AssetUpload = 'asset.upload',
|
||||||
AssetReplace = 'asset.replace',
|
|
||||||
AssetCopy = 'asset.copy',
|
AssetCopy = 'asset.copy',
|
||||||
AssetDerive = 'asset.derive',
|
AssetDerive = 'asset.derive',
|
||||||
|
|
||||||
|
|||||||
@@ -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, AuthSharedLink } from 'src/database';
|
import { AuthSharedLink } from 'src/database';
|
||||||
import {
|
import {
|
||||||
AssetBulkUploadCheckResponseDto,
|
AssetBulkUploadCheckResponseDto,
|
||||||
AssetMediaResponseDto,
|
AssetMediaResponseDto,
|
||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
AssetBulkUploadCheckDto,
|
AssetBulkUploadCheckDto,
|
||||||
AssetMediaCreateDto,
|
AssetMediaCreateDto,
|
||||||
AssetMediaOptionsDto,
|
AssetMediaOptionsDto,
|
||||||
AssetMediaReplaceDto,
|
|
||||||
AssetMediaSize,
|
AssetMediaSize,
|
||||||
CheckExistingAssetsDto,
|
CheckExistingAssetsDto,
|
||||||
UploadFieldName,
|
UploadFieldName,
|
||||||
@@ -24,7 +23,6 @@ import { AssetDownloadOriginalDto } from 'src/dtos/asset.dto';
|
|||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import {
|
import {
|
||||||
AssetFileType,
|
AssetFileType,
|
||||||
AssetStatus,
|
|
||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
CacheControl,
|
CacheControl,
|
||||||
ChecksumAlgorithm,
|
ChecksumAlgorithm,
|
||||||
@@ -164,40 +162,6 @@ export class AssetMediaService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async replaceAsset(
|
|
||||||
auth: AuthDto,
|
|
||||||
id: string,
|
|
||||||
dto: AssetMediaReplaceDto,
|
|
||||||
file: UploadFile,
|
|
||||||
sidecarFile?: UploadFile,
|
|
||||||
): Promise<AssetMediaResponseDto> {
|
|
||||||
try {
|
|
||||||
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] });
|
|
||||||
const asset = await this.assetRepository.getById(id);
|
|
||||||
|
|
||||||
if (!asset) {
|
|
||||||
throw new Error('Asset not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requireQuota(auth, file.size);
|
|
||||||
|
|
||||||
await this.replaceFileData(asset.id, dto, file, sidecarFile?.originalPath);
|
|
||||||
|
|
||||||
// Next, create a backup copy of the existing record. The db record has already been updated above,
|
|
||||||
// but the local variable holds the original file data paths.
|
|
||||||
const copiedPhoto = await this.createCopy(asset);
|
|
||||||
// and immediate trash it
|
|
||||||
await this.assetRepository.updateAll([copiedPhoto.id], { deletedAt: new Date(), status: AssetStatus.Trashed });
|
|
||||||
await this.eventRepository.emit('AssetTrash', { assetId: copiedPhoto.id, userId: auth.user.id });
|
|
||||||
|
|
||||||
await this.userRepository.updateUsage(auth.user.id, file.size);
|
|
||||||
|
|
||||||
return { status: AssetMediaStatus.REPLACED, id: copiedPhoto.id };
|
|
||||||
} catch (error: any) {
|
|
||||||
return this.handleUploadError(error, auth, file, sidecarFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async downloadOriginal(auth: AuthDto, id: string, dto: AssetDownloadOriginalDto): Promise<ImmichFileResponse> {
|
async downloadOriginal(auth: AuthDto, id: string, dto: AssetDownloadOriginalDto): Promise<ImmichFileResponse> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AssetDownload, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AssetDownload, ids: [id] });
|
||||||
|
|
||||||
@@ -367,83 +331,6 @@ export class AssetMediaService extends BaseService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the specified assetId to the specified photo data file properties: checksum, path,
|
|
||||||
* timestamps, deviceIds, and sidecar. Derived properties like: faces, smart search info, etc
|
|
||||||
* are UNTOUCHED. The photo data files modification times on the filesysytem are updated to
|
|
||||||
* the specified timestamps. The exif db record is upserted, and then A METADATA_EXTRACTION
|
|
||||||
* job is queued to update these derived properties.
|
|
||||||
*/
|
|
||||||
private async replaceFileData(
|
|
||||||
assetId: string,
|
|
||||||
dto: AssetMediaReplaceDto,
|
|
||||||
file: UploadFile,
|
|
||||||
sidecarPath?: string,
|
|
||||||
): Promise<void> {
|
|
||||||
await this.assetRepository.update({
|
|
||||||
id: assetId,
|
|
||||||
|
|
||||||
checksum: file.checksum,
|
|
||||||
originalPath: file.originalPath,
|
|
||||||
type: mimeTypes.assetType(file.originalPath),
|
|
||||||
originalFileName: file.originalName,
|
|
||||||
|
|
||||||
deviceAssetId: dto.deviceAssetId,
|
|
||||||
deviceId: dto.deviceId,
|
|
||||||
fileCreatedAt: dto.fileCreatedAt,
|
|
||||||
fileModifiedAt: dto.fileModifiedAt,
|
|
||||||
localDateTime: dto.fileCreatedAt,
|
|
||||||
duration: dto.duration || null,
|
|
||||||
|
|
||||||
livePhotoVideoId: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
await (sidecarPath
|
|
||||||
? this.assetRepository.upsertFile({ assetId, type: AssetFileType.Sidecar, path: sidecarPath })
|
|
||||||
: this.assetRepository.deleteFile({ assetId, type: AssetFileType.Sidecar }));
|
|
||||||
|
|
||||||
await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt));
|
|
||||||
await this.assetRepository.upsertExif(
|
|
||||||
{ assetId, fileSizeInByte: file.size },
|
|
||||||
{ lockedPropertiesBehavior: 'override' },
|
|
||||||
);
|
|
||||||
await this.jobRepository.queue({
|
|
||||||
name: JobName.AssetExtractMetadata,
|
|
||||||
data: { id: assetId, source: 'upload' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a 'shallow' copy of the specified asset record creating a new asset record in the database.
|
|
||||||
* Uses only vital properties excluding things like: stacks, faces, smart search info, etc,
|
|
||||||
* and then queues a METADATA_EXTRACTION job.
|
|
||||||
*/
|
|
||||||
private async createCopy(asset: Omit<Asset, 'id'>) {
|
|
||||||
const created = await this.assetRepository.create({
|
|
||||||
ownerId: asset.ownerId,
|
|
||||||
originalPath: asset.originalPath,
|
|
||||||
originalFileName: asset.originalFileName,
|
|
||||||
libraryId: asset.libraryId,
|
|
||||||
deviceAssetId: asset.deviceAssetId,
|
|
||||||
deviceId: asset.deviceId,
|
|
||||||
type: asset.type,
|
|
||||||
checksum: asset.checksum,
|
|
||||||
checksumAlgorithm: asset.checksumAlgorithm,
|
|
||||||
fileCreatedAt: asset.fileCreatedAt,
|
|
||||||
localDateTime: asset.localDateTime,
|
|
||||||
fileModifiedAt: asset.fileModifiedAt,
|
|
||||||
livePhotoVideoId: asset.livePhotoVideoId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { size } = await this.storageRepository.stat(created.originalPath);
|
|
||||||
await this.assetRepository.upsertExif(
|
|
||||||
{ assetId: created.id, fileSizeInByte: size },
|
|
||||||
{ lockedPropertiesBehavior: 'override' },
|
|
||||||
);
|
|
||||||
await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: created.id, source: 'copy' } });
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async create(ownerId: string, dto: AssetMediaCreateDto, file: UploadFile, sidecarFile?: UploadFile) {
|
private async create(ownerId: string, dto: AssetMediaCreateDto, file: UploadFile, sidecarFile?: UploadFile) {
|
||||||
const asset = await this.assetRepository.create({
|
const asset = await this.assetRepository.create({
|
||||||
ownerId,
|
ownerId,
|
||||||
|
|||||||
Reference in New Issue
Block a user