Skip to content

Commit c10e48c

Browse files
authored
Support attachments in room requests updates + multiregion s3 for assets bucket (#367)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Multi-region asset buckets with lifecycle, intelligent tiering, CORS, and a reusable asset access policy (ARN exposed). * Presigned S3 upload and download endpoints/flows for attachments. * **Enhancements** * UI: dropzone attachments with MIME/size validation, upload on status change, per-item download buttons. * Config: assets bucket id added; data retention extended from 2→4 years. * Lambda modules can accept and attach asset IAM policies. * **Tests** * Unit tests for presign helpers and attachment flows. * **Chores** * Added S3 presigner/client and UI dropzone dependencies. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 1ac0892 commit c10e48c

File tree

22 files changed

+2184
-140
lines changed

22 files changed

+2184
-140
lines changed

src/api/functions/s3.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {
2+
GetObjectCommand,
3+
PutObjectCommand,
4+
type S3Client,
5+
} from "@aws-sdk/client-s3";
6+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
7+
import { InternalServerError } from "common/errors/index.js";
8+
9+
export type CreatePresignedPutInputs = {
10+
s3client: S3Client;
11+
bucketName: string;
12+
key: string;
13+
length: number;
14+
mimeType: string;
15+
md5hash?: string; // Must be a base64-encoded MD5 hash
16+
urlExpiresIn?: number;
17+
};
18+
19+
export async function createPresignedPut({
20+
s3client,
21+
bucketName,
22+
key,
23+
length,
24+
mimeType,
25+
md5hash,
26+
urlExpiresIn,
27+
}: CreatePresignedPutInputs) {
28+
const command = new PutObjectCommand({
29+
Bucket: bucketName,
30+
Key: key,
31+
ContentLength: length,
32+
ContentType: mimeType,
33+
ContentMD5: md5hash,
34+
});
35+
36+
const expiresIn = urlExpiresIn || 900;
37+
38+
try {
39+
return await getSignedUrl(s3client, command, { expiresIn });
40+
} catch (err) {
41+
throw new InternalServerError({
42+
message: "Could not create S3 upload presigned url.",
43+
});
44+
}
45+
}
46+
47+
export type CreatePresignedGetInputs = {
48+
s3client: S3Client;
49+
bucketName: string;
50+
key: string;
51+
urlExpiresIn?: number;
52+
};
53+
54+
export async function createPresignedGet({
55+
s3client,
56+
bucketName,
57+
key,
58+
urlExpiresIn,
59+
}: CreatePresignedGetInputs) {
60+
const command = new GetObjectCommand({
61+
Bucket: bucketName,
62+
Key: key,
63+
});
64+
65+
const expiresIn = urlExpiresIn || 900;
66+
67+
try {
68+
return await getSignedUrl(s3client, command, { expiresIn });
69+
} catch (err) {
70+
throw new InternalServerError({
71+
message: "Could not create S3 download presigned url.",
72+
});
73+
}
74+
}

src/api/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"prettier:write": "prettier --write *.ts **/*.ts"
1616
},
1717
"dependencies": {
18+
"@aws-sdk/s3-request-presigner": "^3.914.0",
19+
"@aws-sdk/client-s3": "^3.914.0",
1820
"@aws-sdk/client-dynamodb": "^3.914.0",
1921
"@aws-sdk/client-lambda": "^3.914.0",
2022
"@aws-sdk/client-secrets-manager": "^3.914.0",

src/api/routes/membership.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
checkExternalMembership,
33
checkPaidMembershipFromTable,
4-
setPaidMembershipInTable,
54
MEMBER_CACHE_SECONDS,
65
getExternalMemberList,
76
patchExternalMemberList,
@@ -13,18 +12,9 @@ import {
1312
InternalServerError,
1413
ValidationError,
1514
} from "common/errors/index.js";
16-
import { getEntraIdToken } from "api/functions/entraId.js";
17-
import { genericConfig, roleArns } from "common/config.js";
18-
import { getRoleCredentials } from "api/functions/sts.js";
19-
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
20-
import {
21-
BatchWriteItemCommand,
22-
DynamoDBClient,
23-
QueryCommand,
24-
ScanCommand,
25-
} from "@aws-sdk/client-dynamodb";
15+
import { genericConfig } from "common/config.js";
16+
import { ScanCommand } from "@aws-sdk/client-dynamodb";
2617
import rateLimiter from "api/plugins/rateLimiter.js";
27-
import { createCheckoutSession } from "api/functions/stripe.js";
2818
import { getSecretValue } from "api/plugins/auth.js";
2919
import stripe, { Stripe } from "stripe";
3020
import { AvailableSQSFunctions, SQSPayload } from "common/types/sqsMessage.js";

src/api/routes/mobileWallet.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { FastifyPluginAsync } from "fastify";
22
import {
33
InternalServerError,
44
UnauthenticatedError,
5-
ValidationError,
65
} from "../../common/errors/index.js";
76
import * as z from "zod/v4";
87
import { checkPaidMembershipFromTable } from "../functions/membership.js";
@@ -16,10 +15,6 @@ import rateLimiter from "api/plugins/rateLimiter.js";
1615
import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
1716
import { withTags } from "api/components/index.js";
1817

19-
const queuedResponseJsonSchema = z.object({
20-
queueId: z.string().uuid(),
21-
});
22-
2318
const mobileWalletRoute: FastifyPluginAsync = async (fastify, _options) => {
2419
fastify.register(rateLimiter, {
2520
limit: 5,

0 commit comments

Comments
 (0)