mirror of
https://github.com/immich-app/immich.git
synced 2025-12-17 17:23:20 +03:00
refactor: maintenanceStatus -> getMaintenanceStatus
refactor: `integrityCheck` -> `detectPriorInstall` chore: add `v2.4.0` version refactor: `/backups/list` -> `/backups` refactor: use sendFile in download route refactor: use separate backups permissions chore: correct descriptions refactor: permit handler that doesn't return promise for sendfile
This commit is contained in:
8
mobile/openapi/README.md
generated
8
mobile/openapi/README.md
generated
@@ -162,14 +162,14 @@ Class | Method | HTTP request | Description
|
|||||||
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library
|
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library
|
||||||
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings
|
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings
|
||||||
*MaintenanceAdminApi* | [**deleteBackup**](doc//MaintenanceAdminApi.md#deletebackup) | **DELETE** /admin/maintenance/backups/{filename} | Delete backup
|
*MaintenanceAdminApi* | [**deleteBackup**](doc//MaintenanceAdminApi.md#deletebackup) | **DELETE** /admin/maintenance/backups/{filename} | Delete backup
|
||||||
|
*MaintenanceAdminApi* | [**detectPriorInstall**](doc//MaintenanceAdminApi.md#detectpriorinstall) | **GET** /admin/maintenance/detect-install | Detect existing install
|
||||||
*MaintenanceAdminApi* | [**downloadBackup**](doc//MaintenanceAdminApi.md#downloadbackup) | **GET** /admin/maintenance/backups/{filename} | Download backup
|
*MaintenanceAdminApi* | [**downloadBackup**](doc//MaintenanceAdminApi.md#downloadbackup) | **GET** /admin/maintenance/backups/{filename} | Download backup
|
||||||
*MaintenanceAdminApi* | [**integrityCheck**](doc//MaintenanceAdminApi.md#integritycheck) | **GET** /admin/maintenance/integrity | Get integrity and heuristics
|
*MaintenanceAdminApi* | [**getMaintenanceStatus**](doc//MaintenanceAdminApi.md#getmaintenancestatus) | **GET** /admin/maintenance/status | Get maintenance mode status
|
||||||
*MaintenanceAdminApi* | [**listBackups**](doc//MaintenanceAdminApi.md#listbackups) | **GET** /admin/maintenance/backups/list | List backups
|
*MaintenanceAdminApi* | [**listBackups**](doc//MaintenanceAdminApi.md#listbackups) | **GET** /admin/maintenance/backups | List backups
|
||||||
*MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode
|
*MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode
|
||||||
*MaintenanceAdminApi* | [**maintenanceStatus**](doc//MaintenanceAdminApi.md#maintenancestatus) | **GET** /admin/maintenance/status | Get maintenance mode status
|
|
||||||
*MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode
|
*MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode
|
||||||
*MaintenanceAdminApi* | [**startRestoreFlow**](doc//MaintenanceAdminApi.md#startrestoreflow) | **POST** /admin/maintenance/backups/restore | Start backup restore flow
|
*MaintenanceAdminApi* | [**startRestoreFlow**](doc//MaintenanceAdminApi.md#startrestoreflow) | **POST** /admin/maintenance/backups/restore | Start backup restore flow
|
||||||
*MaintenanceAdminApi* | [**uploadBackup**](doc//MaintenanceAdminApi.md#uploadbackup) | **POST** /admin/maintenance/backups/upload | Upload asset
|
*MaintenanceAdminApi* | [**uploadBackup**](doc//MaintenanceAdminApi.md#uploadbackup) | **POST** /admin/maintenance/backups/upload | Upload database backup
|
||||||
*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | Retrieve map markers
|
*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | Retrieve map markers
|
||||||
*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode | Reverse geocode coordinates
|
*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode | Reverse geocode coordinates
|
||||||
*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | Add assets to a memory
|
*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | Add assets to a memory
|
||||||
|
|||||||
124
mobile/openapi/lib/api/maintenance_admin_api.dart
generated
124
mobile/openapi/lib/api/maintenance_admin_api.dart
generated
@@ -65,6 +65,54 @@ class MaintenanceAdminApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Detect existing install
|
||||||
|
///
|
||||||
|
/// Collect integrity checks and other heuristics about local data.
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
Future<Response> detectPriorInstallWithHttpInfo() async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final apiPath = r'/admin/maintenance/detect-install';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
apiPath,
|
||||||
|
'GET',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detect existing install
|
||||||
|
///
|
||||||
|
/// Collect integrity checks and other heuristics about local data.
|
||||||
|
Future<MaintenanceIntegrityResponseDto?> detectPriorInstall() async {
|
||||||
|
final response = await detectPriorInstallWithHttpInfo();
|
||||||
|
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), 'MaintenanceIntegrityResponseDto',) as MaintenanceIntegrityResponseDto;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Download backup
|
/// Download backup
|
||||||
///
|
///
|
||||||
/// Downloads the database backup file
|
/// Downloads the database backup file
|
||||||
@@ -122,14 +170,14 @@ class MaintenanceAdminApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get integrity and heuristics
|
/// Get maintenance mode status
|
||||||
///
|
///
|
||||||
/// Collect integrity checks and other heuristics about local data.
|
/// Fetch information about the currently running maintenance action.
|
||||||
///
|
///
|
||||||
/// Note: This method returns the HTTP [Response].
|
/// Note: This method returns the HTTP [Response].
|
||||||
Future<Response> integrityCheckWithHttpInfo() async {
|
Future<Response> getMaintenanceStatusWithHttpInfo() async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/admin/maintenance/integrity';
|
final apiPath = r'/admin/maintenance/status';
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
@@ -152,11 +200,11 @@ class MaintenanceAdminApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get integrity and heuristics
|
/// Get maintenance mode status
|
||||||
///
|
///
|
||||||
/// Collect integrity checks and other heuristics about local data.
|
/// Fetch information about the currently running maintenance action.
|
||||||
Future<MaintenanceIntegrityResponseDto?> integrityCheck() async {
|
Future<MaintenanceStatusResponseDto?> getMaintenanceStatus() async {
|
||||||
final response = await integrityCheckWithHttpInfo();
|
final response = await getMaintenanceStatusWithHttpInfo();
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
@@ -164,7 +212,7 @@ class MaintenanceAdminApi {
|
|||||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||||
// FormatException when trying to decode an empty string.
|
// FormatException when trying to decode an empty string.
|
||||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceIntegrityResponseDto',) as MaintenanceIntegrityResponseDto;
|
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceStatusResponseDto',) as MaintenanceStatusResponseDto;
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -177,7 +225,7 @@ class MaintenanceAdminApi {
|
|||||||
/// Note: This method returns the HTTP [Response].
|
/// Note: This method returns the HTTP [Response].
|
||||||
Future<Response> listBackupsWithHttpInfo() async {
|
Future<Response> listBackupsWithHttpInfo() async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/admin/maintenance/backups/list';
|
final apiPath = r'/admin/maintenance/backups';
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
@@ -274,54 +322,6 @@ class MaintenanceAdminApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get maintenance mode status
|
|
||||||
///
|
|
||||||
/// Fetch information about the currently running maintenance action.
|
|
||||||
///
|
|
||||||
/// Note: This method returns the HTTP [Response].
|
|
||||||
Future<Response> maintenanceStatusWithHttpInfo() async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final apiPath = r'/admin/maintenance/status';
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
const contentTypes = <String>[];
|
|
||||||
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
apiPath,
|
|
||||||
'GET',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get maintenance mode status
|
|
||||||
///
|
|
||||||
/// Fetch information about the currently running maintenance action.
|
|
||||||
Future<MaintenanceStatusResponseDto?> maintenanceStatus() async {
|
|
||||||
final response = await maintenanceStatusWithHttpInfo();
|
|
||||||
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), 'MaintenanceStatusResponseDto',) as MaintenanceStatusResponseDto;
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set maintenance mode
|
/// Set maintenance mode
|
||||||
///
|
///
|
||||||
/// Put Immich into or take it out of maintenance mode
|
/// Put Immich into or take it out of maintenance mode
|
||||||
@@ -410,9 +410,9 @@ class MaintenanceAdminApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload asset
|
/// Upload database backup
|
||||||
///
|
///
|
||||||
/// Uploads a new asset to the server.
|
/// Uploads .sql/.sql.gz file to restore backup from
|
||||||
///
|
///
|
||||||
/// Note: This method returns the HTTP [Response].
|
/// Note: This method returns the HTTP [Response].
|
||||||
///
|
///
|
||||||
@@ -454,9 +454,9 @@ class MaintenanceAdminApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload asset
|
/// Upload database backup
|
||||||
///
|
///
|
||||||
/// Uploads a new asset to the server.
|
/// Uploads .sql/.sql.gz file to restore backup from
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
|
|||||||
12
mobile/openapi/lib/model/permission.dart
generated
12
mobile/openapi/lib/model/permission.dart
generated
@@ -58,6 +58,10 @@ class Permission {
|
|||||||
static const authPeriodChangePassword = Permission._(r'auth.changePassword');
|
static const authPeriodChangePassword = Permission._(r'auth.changePassword');
|
||||||
static const authDevicePeriodDelete = Permission._(r'authDevice.delete');
|
static const authDevicePeriodDelete = Permission._(r'authDevice.delete');
|
||||||
static const archivePeriodRead = Permission._(r'archive.read');
|
static const archivePeriodRead = Permission._(r'archive.read');
|
||||||
|
static const backupPeriodList = Permission._(r'backup.list');
|
||||||
|
static const backupPeriodDownload = Permission._(r'backup.download');
|
||||||
|
static const backupPeriodUpload = Permission._(r'backup.upload');
|
||||||
|
static const backupPeriodDelete = Permission._(r'backup.delete');
|
||||||
static const duplicatePeriodRead = Permission._(r'duplicate.read');
|
static const duplicatePeriodRead = Permission._(r'duplicate.read');
|
||||||
static const duplicatePeriodDelete = Permission._(r'duplicate.delete');
|
static const duplicatePeriodDelete = Permission._(r'duplicate.delete');
|
||||||
static const facePeriodCreate = Permission._(r'face.create');
|
static const facePeriodCreate = Permission._(r'face.create');
|
||||||
@@ -206,6 +210,10 @@ class Permission {
|
|||||||
authPeriodChangePassword,
|
authPeriodChangePassword,
|
||||||
authDevicePeriodDelete,
|
authDevicePeriodDelete,
|
||||||
archivePeriodRead,
|
archivePeriodRead,
|
||||||
|
backupPeriodList,
|
||||||
|
backupPeriodDownload,
|
||||||
|
backupPeriodUpload,
|
||||||
|
backupPeriodDelete,
|
||||||
duplicatePeriodRead,
|
duplicatePeriodRead,
|
||||||
duplicatePeriodDelete,
|
duplicatePeriodDelete,
|
||||||
facePeriodCreate,
|
facePeriodCreate,
|
||||||
@@ -389,6 +397,10 @@ class PermissionTypeTransformer {
|
|||||||
case r'auth.changePassword': return Permission.authPeriodChangePassword;
|
case r'auth.changePassword': return Permission.authPeriodChangePassword;
|
||||||
case r'authDevice.delete': return Permission.authDevicePeriodDelete;
|
case r'authDevice.delete': return Permission.authDevicePeriodDelete;
|
||||||
case r'archive.read': return Permission.archivePeriodRead;
|
case r'archive.read': return Permission.archivePeriodRead;
|
||||||
|
case r'backup.list': return Permission.backupPeriodList;
|
||||||
|
case r'backup.download': return Permission.backupPeriodDownload;
|
||||||
|
case r'backup.upload': return Permission.backupPeriodUpload;
|
||||||
|
case r'backup.delete': return Permission.backupPeriodDelete;
|
||||||
case r'duplicate.read': return Permission.duplicatePeriodRead;
|
case r'duplicate.read': return Permission.duplicatePeriodRead;
|
||||||
case r'duplicate.delete': return Permission.duplicatePeriodDelete;
|
case r'duplicate.delete': return Permission.duplicatePeriodDelete;
|
||||||
case r'face.create': return Permission.facePeriodCreate;
|
case r'face.create': return Permission.facePeriodCreate;
|
||||||
|
|||||||
@@ -372,7 +372,7 @@
|
|||||||
"x-immich-state": "Alpha"
|
"x-immich-state": "Alpha"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/admin/maintenance/backups/list": {
|
"/admin/maintenance/backups": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get the list of the successful and failed backups",
|
"description": "Get the list of the successful and failed backups",
|
||||||
"operationId": "listBackups",
|
"operationId": "listBackups",
|
||||||
@@ -407,11 +407,11 @@
|
|||||||
"x-immich-admin-only": true,
|
"x-immich-admin-only": true,
|
||||||
"x-immich-history": [
|
"x-immich-history": [
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Added"
|
"state": "Added"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Alpha"
|
"state": "Alpha"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -435,11 +435,11 @@
|
|||||||
],
|
],
|
||||||
"x-immich-history": [
|
"x-immich-history": [
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Added"
|
"state": "Added"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Alpha"
|
"state": "Alpha"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -448,7 +448,7 @@
|
|||||||
},
|
},
|
||||||
"/admin/maintenance/backups/upload": {
|
"/admin/maintenance/backups/upload": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Uploads a new asset to the server.",
|
"description": "Uploads .sql/.sql.gz file to restore backup from",
|
||||||
"operationId": "uploadBackup",
|
"operationId": "uploadBackup",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
@@ -478,22 +478,22 @@
|
|||||||
"api_key": []
|
"api_key": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"summary": "Upload asset",
|
"summary": "Upload database backup",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Maintenance (admin)"
|
"Maintenance (admin)"
|
||||||
],
|
],
|
||||||
"x-immich-admin-only": true,
|
"x-immich-admin-only": true,
|
||||||
"x-immich-history": [
|
"x-immich-history": [
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Added"
|
"state": "Added"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Alpha"
|
"state": "Alpha"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"x-immich-permission": "maintenance",
|
"x-immich-permission": "backup.upload",
|
||||||
"x-immich-state": "Alpha"
|
"x-immich-state": "Alpha"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -535,15 +535,15 @@
|
|||||||
"x-immich-admin-only": true,
|
"x-immich-admin-only": true,
|
||||||
"x-immich-history": [
|
"x-immich-history": [
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Added"
|
"state": "Added"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Alpha"
|
"state": "Alpha"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"x-immich-permission": "maintenance",
|
"x-immich-permission": "backup.delete",
|
||||||
"x-immich-state": "Alpha"
|
"x-immich-state": "Alpha"
|
||||||
},
|
},
|
||||||
"get": {
|
"get": {
|
||||||
@@ -591,22 +591,22 @@
|
|||||||
"x-immich-admin-only": true,
|
"x-immich-admin-only": true,
|
||||||
"x-immich-history": [
|
"x-immich-history": [
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Added"
|
"state": "Added"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Alpha"
|
"state": "Alpha"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"x-immich-permission": "maintenance",
|
"x-immich-permission": "backup.download",
|
||||||
"x-immich-state": "Alpha"
|
"x-immich-state": "Alpha"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/admin/maintenance/integrity": {
|
"/admin/maintenance/detect-install": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Collect integrity checks and other heuristics about local data.",
|
"description": "Collect integrity checks and other heuristics about local data.",
|
||||||
"operationId": "integrityCheck",
|
"operationId": "detectPriorInstall",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
@@ -620,17 +620,17 @@
|
|||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"summary": "Get integrity and heuristics",
|
"summary": "Detect existing install",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Maintenance (admin)"
|
"Maintenance (admin)"
|
||||||
],
|
],
|
||||||
"x-immich-history": [
|
"x-immich-history": [
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Added"
|
"state": "Added"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Alpha"
|
"state": "Alpha"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -684,7 +684,7 @@
|
|||||||
"/admin/maintenance/status": {
|
"/admin/maintenance/status": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Fetch information about the currently running maintenance action.",
|
"description": "Fetch information about the currently running maintenance action.",
|
||||||
"operationId": "maintenanceStatus",
|
"operationId": "getMaintenanceStatus",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
@@ -704,11 +704,11 @@
|
|||||||
],
|
],
|
||||||
"x-immich-history": [
|
"x-immich-history": [
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Added"
|
"state": "Added"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "v9.9.9",
|
"version": "v2.4.0",
|
||||||
"state": "Alpha"
|
"state": "Alpha"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -18250,6 +18250,10 @@
|
|||||||
"auth.changePassword",
|
"auth.changePassword",
|
||||||
"authDevice.delete",
|
"authDevice.delete",
|
||||||
"archive.read",
|
"archive.read",
|
||||||
|
"backup.list",
|
||||||
|
"backup.download",
|
||||||
|
"backup.upload",
|
||||||
|
"backup.delete",
|
||||||
"duplicate.read",
|
"duplicate.read",
|
||||||
"duplicate.delete",
|
"duplicate.delete",
|
||||||
"face.create",
|
"face.create",
|
||||||
|
|||||||
@@ -1891,7 +1891,7 @@ export function listBackups(opts?: Oazapfts.RequestOpts) {
|
|||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
data: MaintenanceListBackupsResponseDto;
|
data: MaintenanceListBackupsResponseDto;
|
||||||
}>("/admin/maintenance/backups/list", {
|
}>("/admin/maintenance/backups", {
|
||||||
...opts
|
...opts
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1905,7 +1905,7 @@ export function startRestoreFlow(opts?: Oazapfts.RequestOpts) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Upload asset
|
* Upload database backup
|
||||||
*/
|
*/
|
||||||
export function uploadBackup({ maintenanceUploadBackupDto }: {
|
export function uploadBackup({ maintenanceUploadBackupDto }: {
|
||||||
maintenanceUploadBackupDto: MaintenanceUploadBackupDto;
|
maintenanceUploadBackupDto: MaintenanceUploadBackupDto;
|
||||||
@@ -1941,13 +1941,13 @@ export function downloadBackup({ filename }: {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get integrity and heuristics
|
* Detect existing install
|
||||||
*/
|
*/
|
||||||
export function integrityCheck(opts?: Oazapfts.RequestOpts) {
|
export function detectPriorInstall(opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
data: MaintenanceIntegrityResponseDto;
|
data: MaintenanceIntegrityResponseDto;
|
||||||
}>("/admin/maintenance/integrity", {
|
}>("/admin/maintenance/detect-install", {
|
||||||
...opts
|
...opts
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1969,7 +1969,7 @@ export function maintenanceLogin({ maintenanceLoginDto }: {
|
|||||||
/**
|
/**
|
||||||
* Get maintenance mode status
|
* Get maintenance mode status
|
||||||
*/
|
*/
|
||||||
export function maintenanceStatus(opts?: Oazapfts.RequestOpts) {
|
export function getMaintenanceStatus(opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
data: MaintenanceStatusResponseDto;
|
data: MaintenanceStatusResponseDto;
|
||||||
@@ -5343,6 +5343,10 @@ export enum Permission {
|
|||||||
AuthChangePassword = "auth.changePassword",
|
AuthChangePassword = "auth.changePassword",
|
||||||
AuthDeviceDelete = "authDevice.delete",
|
AuthDeviceDelete = "authDevice.delete",
|
||||||
ArchiveRead = "archive.read",
|
ArchiveRead = "archive.read",
|
||||||
|
BackupList = "backup.list",
|
||||||
|
BackupDownload = "backup.download",
|
||||||
|
BackupUpload = "backup.upload",
|
||||||
|
BackupDelete = "backup.delete",
|
||||||
DuplicateRead = "duplicate.read",
|
DuplicateRead = "duplicate.read",
|
||||||
DuplicateDelete = "duplicate.delete",
|
DuplicateDelete = "duplicate.delete",
|
||||||
FaceCreate = "face.create",
|
FaceCreate = "face.create",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
|
Next,
|
||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Res,
|
Res,
|
||||||
@@ -12,7 +13,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
import { Response } from 'express';
|
import { NextFunction, Response } from 'express';
|
||||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import {
|
import {
|
||||||
@@ -26,9 +27,10 @@ import {
|
|||||||
} from 'src/dtos/maintenance.dto';
|
} from 'src/dtos/maintenance.dto';
|
||||||
import { ApiTag, ImmichCookie, MaintenanceAction, Permission } from 'src/enum';
|
import { ApiTag, ImmichCookie, MaintenanceAction, Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated, FileResponse, GetLoginDetails } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated, FileResponse, GetLoginDetails } from 'src/middleware/auth.guard';
|
||||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { LoginDetails } from 'src/services/auth.service';
|
import { LoginDetails } from 'src/services/auth.service';
|
||||||
import { MaintenanceService } from 'src/services/maintenance.service';
|
import { MaintenanceService } from 'src/services/maintenance.service';
|
||||||
|
import { sendFile } from 'src/utils/file';
|
||||||
import { respondWithCookie } from 'src/utils/response';
|
import { respondWithCookie } from 'src/utils/response';
|
||||||
import { FilenameParamDto } from 'src/validation';
|
import { FilenameParamDto } from 'src/validation';
|
||||||
|
|
||||||
@@ -36,30 +38,30 @@ import { FilenameParamDto } from 'src/validation';
|
|||||||
@Controller('admin/maintenance')
|
@Controller('admin/maintenance')
|
||||||
export class MaintenanceController {
|
export class MaintenanceController {
|
||||||
constructor(
|
constructor(
|
||||||
|
private logger: LoggingRepository,
|
||||||
private service: MaintenanceService,
|
private service: MaintenanceService,
|
||||||
private storageRepository: StorageRepository,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('status')
|
@Get('status')
|
||||||
@Endpoint({
|
@Endpoint({
|
||||||
summary: 'Get maintenance mode status',
|
summary: 'Get maintenance mode status',
|
||||||
description: 'Fetch information about the currently running maintenance action.',
|
description: 'Fetch information about the currently running maintenance action.',
|
||||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
|
||||||
})
|
})
|
||||||
maintenanceStatus(): MaintenanceStatusResponseDto {
|
getMaintenanceStatus(): MaintenanceStatusResponseDto {
|
||||||
return {
|
return {
|
||||||
action: MaintenanceAction.End,
|
action: MaintenanceAction.End,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('integrity')
|
@Get('detect-install')
|
||||||
@Endpoint({
|
@Endpoint({
|
||||||
summary: 'Get integrity and heuristics',
|
summary: 'Detect existing install',
|
||||||
description: 'Collect integrity checks and other heuristics about local data.',
|
description: 'Collect integrity checks and other heuristics about local data.',
|
||||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
|
||||||
})
|
})
|
||||||
integrityCheck(): Promise<MaintenanceIntegrityResponseDto> {
|
detectPriorInstall(): Promise<MaintenanceIntegrityResponseDto> {
|
||||||
return this.service.integrityCheck();
|
return this.service.detectPriorInstall();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
@@ -94,11 +96,11 @@ export class MaintenanceController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('backups/list')
|
@Get('backups')
|
||||||
@Endpoint({
|
@Endpoint({
|
||||||
summary: 'List backups',
|
summary: 'List backups',
|
||||||
description: 'Get the list of the successful and failed backups',
|
description: 'Get the list of the successful and failed backups',
|
||||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
|
||||||
})
|
})
|
||||||
@Authenticated({ permission: Permission.Maintenance, admin: true })
|
@Authenticated({ permission: Permission.Maintenance, admin: true })
|
||||||
listBackups(): Promise<MaintenanceListBackupsResponseDto> {
|
listBackups(): Promise<MaintenanceListBackupsResponseDto> {
|
||||||
@@ -110,21 +112,24 @@ export class MaintenanceController {
|
|||||||
@Endpoint({
|
@Endpoint({
|
||||||
summary: 'Download backup',
|
summary: 'Download backup',
|
||||||
description: 'Downloads the database backup file',
|
description: 'Downloads the database backup file',
|
||||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
|
||||||
})
|
})
|
||||||
@Authenticated({ permission: Permission.Maintenance, admin: true })
|
@Authenticated({ permission: Permission.BackupDownload, admin: true })
|
||||||
downloadBackup(@Param() { filename }: FilenameParamDto, @Res() res: Response) {
|
async downloadBackup(
|
||||||
res.header('Content-Disposition', 'attachment');
|
@Param() { filename }: FilenameParamDto,
|
||||||
res.sendFile(this.service.getBackupPath(filename));
|
@Res() res: Response,
|
||||||
|
@Next() next: NextFunction,
|
||||||
|
): Promise<void> {
|
||||||
|
await sendFile(res, next, () => this.service.downloadBackup(filename), this.logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('backups/:filename')
|
@Delete('backups/:filename')
|
||||||
@Endpoint({
|
@Endpoint({
|
||||||
summary: 'Delete backup',
|
summary: 'Delete backup',
|
||||||
description: 'Delete a backup by its filename',
|
description: 'Delete a backup by its filename',
|
||||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
|
||||||
})
|
})
|
||||||
@Authenticated({ permission: Permission.Maintenance, admin: true })
|
@Authenticated({ permission: Permission.BackupDelete, admin: true })
|
||||||
async deleteBackup(@Param() { filename }: FilenameParamDto): Promise<void> {
|
async deleteBackup(@Param() { filename }: FilenameParamDto): Promise<void> {
|
||||||
return this.service.deleteBackup(filename);
|
return this.service.deleteBackup(filename);
|
||||||
}
|
}
|
||||||
@@ -133,7 +138,7 @@ export class MaintenanceController {
|
|||||||
@Endpoint({
|
@Endpoint({
|
||||||
summary: 'Start backup restore flow',
|
summary: 'Start backup restore flow',
|
||||||
description: 'Put Immich into maintenance mode to restore a backup (Immich must not be configured)',
|
description: 'Put Immich into maintenance mode to restore a backup (Immich must not be configured)',
|
||||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
|
||||||
})
|
})
|
||||||
async startRestoreFlow(
|
async startRestoreFlow(
|
||||||
@GetLoginDetails() loginDetails: LoginDetails,
|
@GetLoginDetails() loginDetails: LoginDetails,
|
||||||
@@ -147,13 +152,13 @@ export class MaintenanceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('backups/upload')
|
@Post('backups/upload')
|
||||||
@Authenticated({ permission: Permission.Maintenance, admin: true })
|
@Authenticated({ permission: Permission.BackupUpload, admin: true })
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiConsumes('multipart/form-data')
|
||||||
@ApiBody({ description: 'Backup Upload', type: MaintenanceUploadBackupDto })
|
@ApiBody({ description: 'Backup Upload', type: MaintenanceUploadBackupDto })
|
||||||
@Endpoint({
|
@Endpoint({
|
||||||
summary: 'Upload asset',
|
summary: 'Upload database backup',
|
||||||
description: 'Uploads a new asset to the server.',
|
description: 'Uploads .sql/.sql.gz file to restore backup from',
|
||||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'),
|
||||||
})
|
})
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
uploadBackup(
|
uploadBackup(
|
||||||
|
|||||||
@@ -127,6 +127,11 @@ export enum Permission {
|
|||||||
|
|
||||||
ArchiveRead = 'archive.read',
|
ArchiveRead = 'archive.read',
|
||||||
|
|
||||||
|
BackupList = 'backup.list',
|
||||||
|
BackupDownload = 'backup.download',
|
||||||
|
BackupUpload = 'backup.upload',
|
||||||
|
BackupDelete = 'backup.delete',
|
||||||
|
|
||||||
DuplicateRead = 'duplicate.read',
|
DuplicateRead = 'duplicate.read',
|
||||||
DuplicateDelete = 'duplicate.delete',
|
DuplicateDelete = 'duplicate.delete',
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export class MaintenanceWorkerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('admin/maintenance/integrity')
|
@Get('admin/maintenance/integrity')
|
||||||
integrityCheck(): Promise<MaintenanceIntegrityResponseDto> {
|
detectPriorInstall(): Promise<MaintenanceIntegrityResponseDto> {
|
||||||
return this.service.integrityCheck();
|
return this.service.detectPriorInstall();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('admin/maintenance/login')
|
@Post('admin/maintenance/login')
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ describe(MaintenanceWorkerService.name, () => {
|
|||||||
mocks.storage.readFile.mockResolvedValue(undefined as never);
|
mocks.storage.readFile.mockResolvedValue(undefined as never);
|
||||||
mocks.storage.overwriteFile.mockRejectedValue(undefined as never);
|
mocks.storage.overwriteFile.mockRejectedValue(undefined as never);
|
||||||
|
|
||||||
await expect(sut.integrityCheck()).resolves.toMatchInlineSnapshot(`
|
await expect(sut.detectPriorInstall()).resolves.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"storage": [
|
"storage": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { type ServerService as _ServerService } from 'src/services/server.servic
|
|||||||
import { MaintenanceModeState } from 'src/types';
|
import { MaintenanceModeState } from 'src/types';
|
||||||
import { deleteBackup, isValidBackupName, listBackups, restoreBackup, uploadBackup } from 'src/utils/backups';
|
import { deleteBackup, isValidBackupName, listBackups, restoreBackup, uploadBackup } from 'src/utils/backups';
|
||||||
import { getConfig } from 'src/utils/config';
|
import { getConfig } from 'src/utils/config';
|
||||||
import { createMaintenanceLoginUrl, integrityCheck } from 'src/utils/maintenance';
|
import { createMaintenanceLoginUrl, detectPriorInstall } from 'src/utils/maintenance';
|
||||||
import { getExternalDomain } from 'src/utils/misc';
|
import { getExternalDomain } from 'src/utils/misc';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,8 +198,8 @@ export class MaintenanceWorkerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
integrityCheck(): Promise<MaintenanceIntegrityResponseDto> {
|
detectPriorInstall(): Promise<MaintenanceIntegrityResponseDto> {
|
||||||
return integrityCheck(this.storageRepository);
|
return detectPriorInstall(this.storageRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(jwt?: string): Promise<MaintenanceAuthDto> {
|
async login(jwt?: string): Promise<MaintenanceAuthDto> {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ describe(MaintenanceService.name, () => {
|
|||||||
mocks.storage.readFile.mockResolvedValue(undefined as never);
|
mocks.storage.readFile.mockResolvedValue(undefined as never);
|
||||||
mocks.storage.overwriteFile.mockRejectedValue(undefined as never);
|
mocks.storage.overwriteFile.mockRejectedValue(undefined as never);
|
||||||
|
|
||||||
await expect(sut.integrityCheck()).resolves.toMatchInlineSnapshot(`
|
await expect(sut.detectPriorInstall()).resolves.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"storage": [
|
"storage": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ import { basename, join } from 'node:path';
|
|||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { MaintenanceAuthDto, MaintenanceIntegrityResponseDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.dto';
|
import { MaintenanceAuthDto, MaintenanceIntegrityResponseDto, SetMaintenanceModeDto } from 'src/dtos/maintenance.dto';
|
||||||
import { MaintenanceAction, StorageFolder, SystemMetadataKey } from 'src/enum';
|
import { CacheControl, MaintenanceAction, StorageFolder, SystemMetadataKey } from 'src/enum';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { MaintenanceModeState } from 'src/types';
|
import { MaintenanceModeState } from 'src/types';
|
||||||
import { deleteBackup, isValidBackupName, listBackups, uploadBackup } from 'src/utils/backups';
|
import { deleteBackup, isValidBackupName, listBackups, uploadBackup } from 'src/utils/backups';
|
||||||
|
import { ImmichFileResponse } from 'src/utils/file';
|
||||||
import {
|
import {
|
||||||
createMaintenanceLoginUrl,
|
createMaintenanceLoginUrl,
|
||||||
|
detectPriorInstall,
|
||||||
generateMaintenanceSecret,
|
generateMaintenanceSecret,
|
||||||
integrityCheck,
|
|
||||||
signMaintenanceJwt,
|
signMaintenanceJwt,
|
||||||
} from 'src/utils/maintenance';
|
} from 'src/utils/maintenance';
|
||||||
import { getExternalDomain } from 'src/utils/misc';
|
import { getExternalDomain } from 'src/utils/misc';
|
||||||
@@ -26,8 +27,8 @@ export class MaintenanceService extends BaseService {
|
|||||||
.then((state) => state ?? { isMaintenanceMode: false });
|
.then((state) => state ?? { isMaintenanceMode: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
integrityCheck(): Promise<MaintenanceIntegrityResponseDto> {
|
detectPriorInstall(): Promise<MaintenanceIntegrityResponseDto> {
|
||||||
return integrityCheck(this.storageRepository);
|
return detectPriorInstall(this.storageRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
async startMaintenance(action: SetMaintenanceModeDto, username: string): Promise<{ jwt: string }> {
|
async startMaintenance(action: SetMaintenanceModeDto, username: string): Promise<{ jwt: string }> {
|
||||||
@@ -98,6 +99,15 @@ export class MaintenanceService extends BaseService {
|
|||||||
return uploadBackup(this.backupRepos, file);
|
return uploadBackup(this.backupRepos, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadBackup(fileName: string): ImmichFileResponse {
|
||||||
|
return {
|
||||||
|
fileName,
|
||||||
|
cacheControl: CacheControl.PrivateWithoutCache,
|
||||||
|
contentType: fileName.endsWith('.gz') ? 'application/gzip' : 'application/sql',
|
||||||
|
path: this.getBackupPath(fileName),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getBackupPath(filename: string): string {
|
getBackupPath(filename: string): string {
|
||||||
if (!isValidBackupName(filename)) {
|
if (!isValidBackupName(filename)) {
|
||||||
throw new BadRequestException('Invalid backup name!');
|
throw new BadRequestException('Invalid backup name!');
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const cacheControlHeaders: Record<CacheControl, string | null> = {
|
|||||||
export const sendFile = async (
|
export const sendFile = async (
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction,
|
||||||
handler: () => Promise<ImmichFileResponse>,
|
handler: () => Promise<ImmichFileResponse> | ImmichFileResponse,
|
||||||
logger: LoggingRepository,
|
logger: LoggingRepository,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
// promisified version of 'res.sendFile' for cleaner async handling
|
// promisified version of 'res.sendFile' for cleaner async handling
|
||||||
|
|||||||
@@ -77,7 +77,9 @@ export function generateMaintenanceSecret(): string {
|
|||||||
return randomBytes(64).toString('hex');
|
return randomBytes(64).toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function integrityCheck(storageRepository: StorageRepository): Promise<MaintenanceIntegrityResponseDto> {
|
export async function detectPriorInstall(
|
||||||
|
storageRepository: StorageRepository,
|
||||||
|
): Promise<MaintenanceIntegrityResponseDto> {
|
||||||
return {
|
return {
|
||||||
storage: await Promise.all(
|
storage: await Promise.all(
|
||||||
Object.values(StorageFolder).map(async (folder) => {
|
Object.values(StorageFolder).map(async (folder) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MaintenanceBackupsList from '$lib/components/maintenance/MaintenanceBackupsList.svelte';
|
import MaintenanceBackupsList from '$lib/components/maintenance/MaintenanceBackupsList.svelte';
|
||||||
import { integrityCheck, type MaintenanceIntegrityResponseDto } from '@immich/sdk';
|
import { detectPriorInstall, type MaintenanceIntegrityResponseDto } from '@immich/sdk';
|
||||||
import { Button, Card, CardBody, Heading, HStack, Icon, Scrollable, Stack, Text } from '@immich/ui';
|
import { Button, Card, CardBody, Heading, HStack, Icon, Scrollable, Stack, Text } from '@immich/ui';
|
||||||
import { mdiAlert, mdiArrowLeft, mdiArrowRight, mdiCheck, mdiClose, mdiRefresh } from '@mdi/js';
|
import { mdiAlert, mdiArrowLeft, mdiArrowRight, mdiCheck, mdiClose, mdiRefresh } from '@mdi/js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
let integrity: MaintenanceIntegrityResponseDto | undefined = $state();
|
let integrity: MaintenanceIntegrityResponseDto | undefined = $state();
|
||||||
|
|
||||||
async function reload() {
|
async function reload() {
|
||||||
integrity = await integrityCheck();
|
integrity = await detectPriorInstall();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(reload);
|
onMount(reload);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { maintenanceStore } from '$lib/stores/maintenance.store';
|
import { maintenanceStore } from '$lib/stores/maintenance.store';
|
||||||
import { websocketStore } from '$lib/stores/websocket';
|
import { websocketStore } from '$lib/stores/websocket';
|
||||||
import { MaintenanceAction, maintenanceLogin, maintenanceStatus } from '@immich/sdk';
|
import { MaintenanceAction, maintenanceLogin } from '@immich/sdk';
|
||||||
|
|
||||||
export function maintenanceCreateUrl(url: URL) {
|
export function maintenanceCreateUrl(url: URL) {
|
||||||
const target = new URL(AppRoute.MAINTENANCE, url.origin);
|
const target = new URL(AppRoute.MAINTENANCE, url.origin);
|
||||||
@@ -36,7 +36,7 @@ export const loadMaintenanceAuth = async () => {
|
|||||||
export const loadMaintenanceStatus = async () => {
|
export const loadMaintenanceStatus = async () => {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const status = await maintenanceStatus();
|
const status = await getMaintenanceStatus();
|
||||||
maintenanceStore.status.set(status);
|
maintenanceStore.status.set(status);
|
||||||
|
|
||||||
if (status.action === MaintenanceAction.End) {
|
if (status.action === MaintenanceAction.End) {
|
||||||
|
|||||||
Reference in New Issue
Block a user