Skip to content

Commit 28663be

Browse files
committed
Allow file urls option to download file or not (disposition header)
1 parent b151a88 commit 28663be

File tree

6 files changed

+29
-11
lines changed

6 files changed

+29
-11
lines changed

src/components/file/file-url.controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
HttpStatus,
66
Inject,
77
Param,
8+
Query,
89
Request,
910
Response,
1011
} from '@nestjs/common';
@@ -29,6 +30,7 @@ export class FileUrlController {
2930
@Get(':fileId/:fileName?')
3031
async download(
3132
@Param('fileId') fileId: ID,
33+
@Query('download') download: '' | undefined,
3234
@Request() request: IRequest,
3335
@Response() res: unknown,
3436
) {
@@ -41,7 +43,7 @@ export class FileUrlController {
4143

4244
// TODO authorization using session
4345

44-
const url = await this.files.getDownloadUrl(node);
46+
const url = await this.files.getDownloadUrl(node, download != null);
4547
const cacheControl = this.files.determineCacheHeader(node);
4648

4749
const { httpAdapter } = this.httpAdapterHost;

src/components/file/file-url.resolver-util.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ResolveField } from '@nestjs/graphql';
1+
import { Args, ResolveField } from '@nestjs/graphql';
22
import { stripIndent } from 'common-tags';
33
import { URL } from 'url';
44

@@ -10,3 +10,14 @@ export const Resolver = () =>
1010
This url could require authentication.
1111
`,
1212
});
13+
14+
export const DownloadArg = () =>
15+
Args('download', {
16+
description: stripIndent`
17+
Whether the browser should download this file if opened directly
18+
19+
This sets the \`Content-Disposition\` header to \`attachment\` instead of \`inline\`.
20+
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
21+
`,
22+
defaultValue: false,
23+
});

src/components/file/file-version.resolver.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ export class FileVersionResolver {
1010
constructor(protected readonly service: FileService) {}
1111

1212
@FileUrl.Resolver()
13-
async url(@Parent() node: FileVersion) {
14-
return await this.service.getUrl(node);
13+
async url(
14+
@Parent() node: FileVersion,
15+
@FileUrl.DownloadArg() download: boolean,
16+
) {
17+
return await this.service.getUrl(node, download);
1518
}
1619

1720
@ResolveField(() => URL, {

src/components/file/file.resolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ export class FileResolver {
7777
}
7878

7979
@FileUrl.Resolver()
80-
async url(@Parent() node: File) {
81-
return await this.service.getUrl(node);
80+
async url(@Parent() node: File, @FileUrl.DownloadArg() download: boolean) {
81+
return await this.service.getUrl(node, download);
8282
}
8383

8484
@ResolveField(() => URL, {

src/components/file/file.service.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,27 +145,28 @@ export class FileService {
145145
return data;
146146
}
147147

148-
async getUrl(node: FileNode) {
148+
async getUrl(node: FileNode, download: boolean) {
149149
const url = withAddedPath(
150150
this.config.hostUrl,
151151
FileUrl.path,
152152
isFile(node) ? node.latestVersionId : node.id,
153153
encodeURIComponent(node.name),
154154
);
155-
return url.toString();
155+
return url.toString() + (download ? '?download' : '');
156156
}
157157

158-
async getDownloadUrl(node: FileNode): Promise<string> {
158+
async getDownloadUrl(node: FileNode, download = true): Promise<string> {
159159
if (isDirectory(node)) {
160160
throw new InputException('View directories via GraphQL API');
161161
}
162162
const id = isFile(node) ? node.latestVersionId : node.id;
163+
const disposition = download ? 'attachment' : 'inline';
163164
try {
164165
// before sending link, first check if object exists in s3
165166
await this.bucket.headObject(id);
166167
return await this.bucket.getSignedUrl(GetObject, {
167168
Key: id,
168-
ResponseContentDisposition: `attachment; filename="${encodeURIComponent(
169+
ResponseContentDisposition: `${disposition}; filename="${encodeURIComponent(
169170
node.name,
170171
)}"`,
171172
ResponseContentType: node.mimeType,

src/components/file/media-url.resolver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ export class MediaUrlResolver {
1212
@FileUrl.Resolver()
1313
async url(
1414
@Parent() media: Media,
15+
@FileUrl.DownloadArg() download: boolean,
1516
@Loader(FileNodeLoader) files: LoaderOf<FileNodeLoader>,
1617
) {
1718
const node = await files.load(media.file);
18-
return await this.service.getUrl(node);
19+
return await this.service.getUrl(node, download);
1920
}
2021
}

0 commit comments

Comments
 (0)