Skip to content

Commit 7c52a4d

Browse files
committed
feat: integrate presigned URLs for attachments in other costs retrieval
1 parent ec2ae10 commit 7c52a4d

2 files changed

Lines changed: 94 additions & 26 deletions

File tree

src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import request from "supertest";
22
import app from "@src/app";
33
import { tryber } from "@src/features/database";
4+
import { getPresignedUrl } from "@src/features/s3/presignUrl";
5+
6+
jest.mock("@src/features/s3/presignUrl");
7+
8+
const mockedGetPresignedUrl = getPresignedUrl as jest.MockedFunction<
9+
typeof getPresignedUrl
10+
>;
411

512
describe("GET /campaigns/campaignId/finance/otherCosts", () => {
13+
beforeEach(() => {
14+
mockedGetPresignedUrl.mockImplementation(async (url: string) => url);
15+
});
16+
17+
afterEach(() => {
18+
jest.clearAllMocks();
19+
});
620
beforeAll(async () => {
721
await tryber.tables.WpAppqEvdProfile.do().insert([
822
{
@@ -176,11 +190,13 @@ describe("GET /campaigns/campaignId/finance/otherCosts", () => {
176190
id: 1,
177191
url: "https://example.com/attachment1.pdf",
178192
mimetype: "application/pdf",
193+
presigned_url: expect.any(String),
179194
}),
180195
expect.objectContaining({
181196
id: 2,
182197
url: "https://example.com/attachment2.jpg",
183198
mimetype: "image/jpeg",
199+
presigned_url: expect.any(String),
184200
}),
185201
]),
186202
}),
@@ -201,6 +217,7 @@ describe("GET /campaigns/campaignId/finance/otherCosts", () => {
201217
id: 3,
202218
url: "https://example.com/attachment3.png",
203219
mimetype: "image/png",
220+
presigned_url: expect.any(String),
204221
}),
205222
]),
206223
}),
@@ -230,6 +247,20 @@ describe("GET /campaigns/campaignId/finance/otherCosts", () => {
230247
id: 1,
231248
},
232249
description: "Cost 1 description",
250+
attachments: expect.arrayContaining([
251+
expect.objectContaining({
252+
id: 1,
253+
url: "https://example.com/attachment1.pdf",
254+
mimetype: "application/pdf",
255+
presigned_url: expect.any(String),
256+
}),
257+
expect.objectContaining({
258+
id: 2,
259+
url: "https://example.com/attachment2.jpg",
260+
mimetype: "image/jpeg",
261+
presigned_url: expect.any(String),
262+
}),
263+
]),
233264
}),
234265
expect.objectContaining({
235266
cost_id: 2,
@@ -243,6 +274,14 @@ describe("GET /campaigns/campaignId/finance/otherCosts", () => {
243274
id: 2,
244275
},
245276
description: "Cost 2 description",
277+
attachments: expect.arrayContaining([
278+
expect.objectContaining({
279+
id: 3,
280+
url: "https://example.com/attachment3.png",
281+
mimetype: "image/png",
282+
presigned_url: expect.any(String),
283+
}),
284+
]),
246285
}),
247286
]),
248287
})
@@ -307,4 +346,24 @@ describe("GET /campaigns/campaignId/finance/otherCosts", () => {
307346
expect(costWithoutAttachments.cost).toBe(50);
308347
expect(costWithoutAttachments.attachments).toEqual([]);
309348
});
349+
350+
it("Should call getPresignedUrl for each attachment with 3 hours expiration", async () => {
351+
await request(app)
352+
.get("/campaigns/1/finance/otherCosts")
353+
.set("Authorization", "Bearer admin");
354+
355+
expect(mockedGetPresignedUrl).toHaveBeenCalledTimes(3);
356+
expect(mockedGetPresignedUrl).toHaveBeenCalledWith(
357+
"https://example.com/attachment1.pdf",
358+
10800
359+
);
360+
expect(mockedGetPresignedUrl).toHaveBeenCalledWith(
361+
"https://example.com/attachment2.jpg",
362+
10800
363+
);
364+
expect(mockedGetPresignedUrl).toHaveBeenCalledWith(
365+
"https://example.com/attachment3.png",
366+
10800
367+
);
368+
});
310369
});

src/routes/campaigns/campaignId/finance/otherCosts/_get/index.ts

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import CampaignRoute from "@src/features/routes/CampaignRoute";
44
import { tryber } from "@src/features/database";
55
import OpenapiError from "@src/features/OpenapiError";
6+
import { getPresignedUrl } from "@src/features/s3/presignUrl";
67

78
type OtherCost = {
89
cost_id: number;
@@ -20,6 +21,7 @@ type OtherCost = {
2021
id: number;
2122
url: string;
2223
mimetype: string;
24+
presigned_url: string;
2325
}[];
2426
};
2527

@@ -77,31 +79,38 @@ export default class OtherCostsRoute extends CampaignRoute<{
7779
.select("id", "url", "mime_type", "cost_id")
7880
.whereIn("cost_id", costIds);
7981

80-
return costs.map((cost) => {
81-
const type = types.find((t) => t.id === cost.type_id);
82-
const supplier = suppliers.find((s) => s.id === cost.supplier_id);
83-
const costAttachments = attachments.filter(
84-
(a) => a.cost_id === cost.cost_id
85-
);
86-
87-
return {
88-
cost_id: cost.cost_id,
89-
cost: cost.cost,
90-
type: {
91-
name: type?.name || "",
92-
id: type?.id || 0,
93-
},
94-
supplier: {
95-
name: supplier?.name || "",
96-
id: supplier?.id || 0,
97-
},
98-
description: cost.description,
99-
attachments: costAttachments.map((a) => ({
100-
id: a.id,
101-
url: a.url,
102-
mimetype: a.mime_type,
103-
})),
104-
};
105-
});
82+
return Promise.all(
83+
costs.map(async (cost) => {
84+
const type = types.find((t) => t.id === cost.type_id);
85+
const supplier = suppliers.find((s) => s.id === cost.supplier_id);
86+
const costAttachments = attachments.filter(
87+
(a) => a.cost_id === cost.cost_id
88+
);
89+
90+
const resolvedAttachments = await Promise.all(
91+
costAttachments.map(async (a) => ({
92+
id: a.id,
93+
url: a.url,
94+
mimetype: a.mime_type,
95+
presigned_url: await getPresignedUrl(a.url, 10800), // 3 hours expiration
96+
}))
97+
);
98+
99+
return {
100+
cost_id: cost.cost_id,
101+
cost: cost.cost,
102+
type: {
103+
name: type?.name || "",
104+
id: type?.id || 0,
105+
},
106+
supplier: {
107+
name: supplier?.name || "",
108+
id: supplier?.id || 0,
109+
},
110+
description: cost.description,
111+
attachments: resolvedAttachments,
112+
};
113+
})
114+
);
106115
}
107116
}

0 commit comments

Comments
 (0)