Skip to content

Commit 581f8a4

Browse files
committed
refactor(attachments): unify get api
1 parent 03c39ca commit 581f8a4

File tree

5 files changed

+47
-91
lines changed

5 files changed

+47
-91
lines changed

src/attachments/attachments.controller.ts

Lines changed: 7 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@ export class AttachmentsController {
3838
}
3939

4040
@Get(':attachmentId')
41+
@CookieAuth({ onAuthFail: 'continue' })
4142
async downloadAttachment(
42-
@UserId() userId: string,
43+
@Req() req: Request,
44+
@UserId({ optional: true }) userId: string | undefined,
4345
@Param('namespaceId') namespaceId: string,
4446
@Param('resourceId') resourceId: string,
4547
@Param('attachmentId') attachmentId: string,
4648
@Res() res: Response,
4749
) {
50+
if (!userId) {
51+
this.setRedirect(req, res);
52+
return;
53+
}
4854
return await this.attachmentsService.downloadAttachment(
4955
namespaceId,
5056
resourceId,
@@ -61,50 +67,6 @@ export class AttachmentsController {
6167
.redirect(`/user/login?redirect=${encodeURIComponent(req.url)}`);
6268
}
6369

64-
@CookieAuth({ onAuthFail: 'continue' })
65-
@Get(':attachmentId/images')
66-
async displayImage(
67-
@Req() req: Request,
68-
@UserId({ optional: true }) userId: string | undefined,
69-
@Param('namespaceId') namespaceId: string,
70-
@Param('resourceId') resourceId: string,
71-
@Param('attachmentId') attachmentId: string,
72-
@Res() res: Response,
73-
) {
74-
if (userId) {
75-
return await this.attachmentsService.displayMedia(
76-
namespaceId,
77-
resourceId,
78-
attachmentId,
79-
userId,
80-
res,
81-
);
82-
}
83-
this.setRedirect(req, res);
84-
}
85-
86-
@CookieAuth({ onAuthFail: 'continue' })
87-
@Get(':attachmentId/media')
88-
async displayMedia(
89-
@Req() req: Request,
90-
@UserId({ optional: true }) userId: string | undefined,
91-
@Param('namespaceId') namespaceId: string,
92-
@Param('resourceId') resourceId: string,
93-
@Param('attachmentId') attachmentId: string,
94-
@Res() res: Response,
95-
) {
96-
if (userId) {
97-
return await this.attachmentsService.displayMedia(
98-
namespaceId,
99-
resourceId,
100-
attachmentId,
101-
userId,
102-
res,
103-
);
104-
}
105-
this.setRedirect(req, res);
106-
}
107-
10870
@Delete(':attachmentId')
10971
async deleteAttachment(
11072
@UserId() userId: string,

src/attachments/attachments.e2e-spec.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,15 @@ describe('AttachmentsController (e2e)', () => {
146146
expect(response.headers['content-disposition']).toContain(testFilename);
147147
});
148148

149-
it('should reject download without authentication', async () => {
150-
await client
149+
it('should redirect to login when no authentication provided', async () => {
150+
const response = await client
151151
.request()
152152
.get(
153153
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${attachmentId}`,
154154
)
155-
.expect(401);
155+
.expect(302);
156+
157+
expect(response.headers.location).toContain('/user/login');
156158
});
157159

158160
it('should reject download with invalid attachment ID', async () => {
@@ -257,7 +259,7 @@ describe('AttachmentsController (e2e)', () => {
257259
});
258260
});
259261

260-
describe('GET /api/v1/namespaces/:namespaceId/resources/:resourceId/attachments/:attachmentId/images (Public)', () => {
262+
describe('GET /api/v1/namespaces/:namespaceId/resources/:resourceId/attachments/:attachmentId (Public)', () => {
261263
let imageAttachmentId: string;
262264
const imageContent = Buffer.from('fake-image-content');
263265
const imageFilename = 'test-image.png';
@@ -278,7 +280,7 @@ describe('AttachmentsController (e2e)', () => {
278280
const response = await client
279281
.request()
280282
.get(
281-
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${imageAttachmentId}/images`,
283+
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${imageAttachmentId}`,
282284
)
283285
.expect(HttpStatus.FOUND);
284286

@@ -290,7 +292,7 @@ describe('AttachmentsController (e2e)', () => {
290292
const response = await client
291293
.request()
292294
.get(
293-
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${imageAttachmentId}/images`,
295+
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${imageAttachmentId}`,
294296
)
295297
.set('Cookie', `token=${client.user.token}`)
296298
.expect(200);
@@ -299,7 +301,7 @@ describe('AttachmentsController (e2e)', () => {
299301
});
300302
});
301303

302-
describe('GET /api/v1/namespaces/:namespaceId/resources/:resourceId/attachments/:attachmentId/media (Public)', () => {
304+
describe('GET /api/v1/namespaces/:namespaceId/resources/:resourceId/attachments/:attachmentId (Public)', () => {
303305
let mediaAttachmentId: string;
304306
const mediaContent = Buffer.from('fake-media-content');
305307
const mediaFilename = 'test-media.mp3';
@@ -320,7 +322,7 @@ describe('AttachmentsController (e2e)', () => {
320322
const response = await client
321323
.request()
322324
.get(
323-
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${mediaAttachmentId}/media`,
325+
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${mediaAttachmentId}`,
324326
)
325327
.expect(HttpStatus.FOUND);
326328

@@ -332,7 +334,7 @@ describe('AttachmentsController (e2e)', () => {
332334
const response = await client
333335
.request()
334336
.get(
335-
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${mediaAttachmentId}/media`,
337+
`/api/v1/namespaces/${client.namespace.id}/resources/${testResourceId}/attachments/${mediaAttachmentId}`,
336338
)
337339
.set('Cookie', `token=${client.user.token}`)
338340
.expect(200);

src/attachments/attachments.service.ts

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
getOriginalFileName,
44
} from 'omniboxd/utils/encode-filename';
55
import {
6-
BadRequestException,
76
ForbiddenException,
87
Injectable,
98
Logger,
@@ -166,7 +165,13 @@ export class AttachmentsService {
166165
const objectResponse = await this.minioService.get(
167166
this.minioPath(attachmentId),
168167
);
169-
return objectStreamResponse(objectResponse, httpResponse);
168+
169+
// Display media files inline, download other files as attachments
170+
const forceDownload = !this.isMedia(objectResponse.mimetype);
171+
172+
return objectStreamResponse(objectResponse, httpResponse, {
173+
forceDownload,
174+
});
170175
}
171176

172177
async deleteAttachment(
@@ -199,35 +204,6 @@ export class AttachmentsService {
199204
return false;
200205
}
201206

202-
async displayMedia(
203-
namespaceId: string,
204-
resourceId: string,
205-
attachmentId: string,
206-
userId: string,
207-
httpResponse: Response,
208-
) {
209-
await this.checkPermission(
210-
namespaceId,
211-
resourceId,
212-
userId,
213-
ResourcePermission.CAN_VIEW,
214-
);
215-
216-
await this.getRelationOrFail(namespaceId, resourceId, attachmentId);
217-
218-
const objectResponse = await this.minioService.get(
219-
this.minioPath(attachmentId),
220-
);
221-
222-
if (!this.isMedia(objectResponse.mimetype)) {
223-
throw new BadRequestException(attachmentId);
224-
}
225-
226-
return objectStreamResponse(objectResponse, httpResponse, {
227-
forceDownload: false,
228-
});
229-
}
230-
231207
async copyAttachmentsToResource(
232208
namespaceId: string,
233209
sourceResourceId: string,

src/resources/resources.controller.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@ import { CreateResourceDto } from 'omniboxd/resources/dto/create-resource.dto';
33
import { UpdateResourceDto } from 'omniboxd/resources/dto/update-resource.dto';
44
import { PermissionsService } from 'omniboxd/permissions/permissions.service';
55
import { ResourcePermission } from 'omniboxd/permissions/resource-permission.enum';
6-
import { Body, Controller, Delete, ForbiddenException, Get, Param, Patch, Post, Query, Req, } from '@nestjs/common';
6+
import {
7+
Body,
8+
Controller,
9+
Delete,
10+
ForbiddenException,
11+
Get,
12+
Param,
13+
Patch,
14+
Post,
15+
Query,
16+
Req,
17+
} from '@nestjs/common';
718
import { UserId } from 'omniboxd/decorators/user-id.decorator';
819
import { Request } from 'express';
920
import { ResourceMetaDto } from 'omniboxd/resources/dto/resource.dto';

test/test-client.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,31 +92,36 @@ export class TestClient {
9292
get(url: string) {
9393
return this.request()
9494
.get(url)
95-
.set('Authorization', `Bearer ${this.user.token}`);
95+
.set('Authorization', `Bearer ${this.user.token}`)
96+
.set('Cookie', `token=${this.user.token}`);
9697
}
9798

9899
post(url: string) {
99100
return this.request()
100101
.post(url)
101-
.set('Authorization', `Bearer ${this.user.token}`);
102+
.set('Authorization', `Bearer ${this.user.token}`)
103+
.set('Cookie', `token=${this.user.token}`);
102104
}
103105

104106
patch(url: string) {
105107
return this.request()
106108
.patch(url)
107-
.set('Authorization', `Bearer ${this.user.token}`);
109+
.set('Authorization', `Bearer ${this.user.token}`)
110+
.set('Cookie', `token=${this.user.token}`);
108111
}
109112

110113
put(url: string) {
111114
return this.request()
112115
.put(url)
113-
.set('Authorization', `Bearer ${this.user.token}`);
116+
.set('Authorization', `Bearer ${this.user.token}`)
117+
.set('Cookie', `token=${this.user.token}`);
114118
}
115119

116120
delete(url: string) {
117121
return this.request()
118122
.delete(url)
119-
.set('Authorization', `Bearer ${this.user.token}`);
123+
.set('Authorization', `Bearer ${this.user.token}`)
124+
.set('Cookie', `token=${this.user.token}`);
120125
}
121126

122127
public static async create(): Promise<TestClient> {

0 commit comments

Comments
 (0)