mirror of
https://github.com/immich-app/immich
synced 2026-08-29 13:15:45 +00:00
refactor(server): parse fields from x-www-form-urlencoded request
This commit is contained in:
parent
0dd39fbb0e
commit
4c1eb53aaf
8 changed files with 36 additions and 40 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Body, Controller, HttpCode, HttpStatus, Post, Res, StreamableFile, UseInterceptors } from '@nestjs/common';
|
||||
import { Body, Controller, HttpCode, HttpStatus, Post, Res, StreamableFile } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
|
|
@ -6,7 +6,6 @@ import { AuthDto } from 'src/dtos/auth.dto';
|
|||
import { DownloadArchiveDto, DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto';
|
||||
import { ApiTag, Permission } from 'src/enum';
|
||||
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
|
||||
import { FormsToJsonInterceptor } from 'src/middleware/forms-to-json.interceptor';
|
||||
import { DownloadService } from 'src/services/download.service';
|
||||
import { asStreamableFile } from 'src/utils/file';
|
||||
|
||||
|
|
@ -31,7 +30,6 @@ export class DownloadController {
|
|||
@Authenticated({ permission: Permission.AssetDownload, sharedLink: true })
|
||||
@FileResponse()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseInterceptors(FormsToJsonInterceptor)
|
||||
@Endpoint({
|
||||
summary: 'Download asset archive',
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ const AssetBulkDeleteSchema = BulkIdsSchema.extend({
|
|||
|
||||
export const AssetIdsSchema = z
|
||||
.object({
|
||||
assetIds: z.array(z.uuidv4()).describe('Asset IDs'),
|
||||
assetIds: z.preprocess((val) => (typeof val === 'string' ? [val] : val), z.array(z.uuidv4())).describe('Asset IDs'),
|
||||
})
|
||||
.meta({ id: 'AssetIdsDto' });
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,18 @@ const DownloadResponseSchema = z
|
|||
.meta({ id: 'DownloadResponseDto' });
|
||||
|
||||
const DownloadArchiveSchema = AssetIdsSchema.extend({
|
||||
edited: z.boolean().optional().describe('Download edited asset if available'),
|
||||
edited: z
|
||||
.preprocess((val) => {
|
||||
if (val === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (val === 'false') {
|
||||
return false;
|
||||
}
|
||||
return val;
|
||||
}, z.boolean())
|
||||
.optional()
|
||||
.describe('Download edited asset if available'),
|
||||
archiveName: z.string().optional().describe('The name of the archive to download, without extension'),
|
||||
}).meta({ id: 'DownloadArchiveDto' });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class FormsToJsonInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const req = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
const contentType = req.headers['content-type'];
|
||||
if (contentType?.startsWith('application/x-www-form-urlencoded')) {
|
||||
try {
|
||||
req.body = JSON.parse(req?.body?.json);
|
||||
req.headers['content-type'] = 'application/json';
|
||||
} catch {
|
||||
// ignore if failed to parse
|
||||
}
|
||||
}
|
||||
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@ import { SvelteMap } from 'svelte/reactivity';
|
|||
|
||||
export interface DownloadState {
|
||||
url: string;
|
||||
payload: unknown;
|
||||
assetIds: string[];
|
||||
archiveName: string;
|
||||
total: number;
|
||||
downloaded: boolean;
|
||||
}
|
||||
|
|
@ -12,8 +13,8 @@ class DownloadManager {
|
|||
|
||||
isDownloading = $derived(this.assets.size > 0);
|
||||
|
||||
add(key: string, url: string, payload: unknown, total: number) {
|
||||
this.assets.set(key, { url, payload, total, downloaded: false });
|
||||
add(key: string, url: string, assetIds: string[], archiveName: string, total: number) {
|
||||
this.assets.set(key, { url, assetIds, archiveName, total, downloaded: false });
|
||||
}
|
||||
|
||||
clearAll() {
|
||||
|
|
|
|||
|
|
@ -293,17 +293,26 @@ export const downloadUrl = (url: string, filename: string) => {
|
|||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const downloadUrlPost = (url: string, data: unknown) => {
|
||||
export const downloadUrlPost = (url: string, assetIds: string[], archiveName: string) => {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'post';
|
||||
form.action = url;
|
||||
form.target = '_blank';
|
||||
|
||||
const inputJson = document.createElement('input');
|
||||
inputJson.type = 'hidden';
|
||||
inputJson.name = 'json';
|
||||
inputJson.value = JSON.stringify(data);
|
||||
form.append(inputJson);
|
||||
function mkInput(name: string, value: string) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = name;
|
||||
input.value = value;
|
||||
form.append(input);
|
||||
}
|
||||
|
||||
for (const assetId of assetIds) {
|
||||
mkInput('assetIds', assetId);
|
||||
}
|
||||
|
||||
mkInput('archiveName', archiveName);
|
||||
mkInput('edited', 'true');
|
||||
|
||||
document.body.append(form);
|
||||
form.submit();
|
||||
|
|
|
|||
|
|
@ -102,13 +102,12 @@ export const downloadArchive = async (fileName: string, options: Omit<DownloadIn
|
|||
}
|
||||
|
||||
const url = getBaseUrl() + '/download/archive' + (queryParams ? `?${queryParams}` : '');
|
||||
const payload = { assetIds: archive.assetIds, edited: true, archiveName };
|
||||
|
||||
try {
|
||||
if (downloadInfo.archives.length > 1) {
|
||||
downloadManager.add(downloadKey, url, payload, archive.size);
|
||||
downloadManager.add(downloadKey, url, archive.assetIds, archiveName, archive.size);
|
||||
} else {
|
||||
downloadUrlPost(url, payload);
|
||||
downloadUrlPost(url, archive.assetIds, archiveName);
|
||||
const $t = await getFormatter();
|
||||
const $locale = get(locale);
|
||||
toastManager.primary(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import { downloadUrlPost } from '$lib/utils';
|
||||
|
||||
const startDownload = (downloadKey: string, download: DownloadState) => {
|
||||
downloadUrlPost(download.url, download.payload);
|
||||
downloadUrlPost(download.url, download.assetIds, download.archiveName);
|
||||
downloadManager.markDownloaded(downloadKey);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue