-
Notifications
You must be signed in to change notification settings - Fork 0
Linkry redirect architecture changes #385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,13 @@ | ||||||||||||||
| import * as z from "zod/v4"; | ||||||||||||||
| import { OrgUniqueId } from "./generic.js"; | ||||||||||||||
|
|
||||||||||||||
| export type ShortLinkEntry = { | ||||||||||||||
| slug: string; | ||||||||||||||
| access: string; | ||||||||||||||
| redir?: string; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| export const LINKRY_MAX_SLUG_LENGTH = 1000; | ||||||||||||||
| export const LINKRY_MAX_SLUG_LENGTH = 100; | ||||||||||||||
|
|
||||||||||||||
| export const getRequest = z.object({ | ||||||||||||||
| slug: z.string().min(1).max(LINKRY_MAX_SLUG_LENGTH).optional() | ||||||||||||||
|
|
@@ -19,7 +20,10 @@ export const linkryAccessList = z.array(z.string().min(1)).meta({ | |||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| export const createRequest = z.object({ | ||||||||||||||
| slug: linkrySlug, | ||||||||||||||
| slug: linkrySlug.refine((url) => !url.includes('#'), { | ||||||||||||||
| message: "Slug must not contain a hashtag" | ||||||||||||||
| }), | ||||||||||||||
|
Comment on lines
+23
to
+25
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good validation addition, but fix formatting. The hashtag validation is sensible since - slug: linkrySlug.refine((url) => !url.includes('#'), {
- message: "Slug must not contain a hashtag"
- }),
+ slug: linkrySlug.refine((url) => !url.includes("#"), {
+ message: "Slug must not contain a hashtag",
+ }),📝 Committable suggestion
Suggested change
🧰 Tools🪛 ESLint[error] 23-23: Replace (prettier/prettier) [error] 24-24: Insert (prettier/prettier) 🤖 Prompt for AI Agents |
||||||||||||||
| orgId: z.optional(OrgUniqueId), | ||||||||||||||
| access: linkryAccessList, | ||||||||||||||
| redirect: z.url().min(1).meta({ description: "Full URL to redirect to when the short URL is visited.", example: "https://google.com" }) | ||||||||||||||
| }); | ||||||||||||||
|
|
@@ -33,14 +37,8 @@ export const linkRecord = z.object({ | |||||||||||||
| owner: z.string().min(1) | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| export const delegatedLinkRecord = linkRecord.extend({ | ||||||||||||||
| owner: z.string().min(1) | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| export type LinkRecord = z.infer<typeof linkRecord>; | ||||||||||||||
|
|
||||||||||||||
| export type DelegatedLinkRecord = z.infer<typeof delegatedLinkRecord>; | ||||||||||||||
|
|
||||||||||||||
| export const getLinksResponse = z.object({ | ||||||||||||||
| ownedLinks: z.array(linkRecord), | ||||||||||||||
| delegatedLinks: z.array(linkRecord) | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||
| /* eslint-disable no-console */ | ||||||
| import esbuild from "esbuild"; | ||||||
|
|
||||||
| const commonParams = { | ||||||
| bundle: true, | ||||||
| format: "esm", | ||||||
| minify: true, | ||||||
| outExtension: { ".js": ".mjs" }, | ||||||
| loader: { | ||||||
| ".png": "file", | ||||||
| ".pkpass": "file", | ||||||
| ".json": "file", | ||||||
| }, // File loaders | ||||||
| target: "es2022", // Target ES2022 | ||||||
| sourcemap: true, | ||||||
| platform: "node", | ||||||
| external: ["@aws-sdk/*"], | ||||||
| banner: { | ||||||
| js: ` | ||||||
| import path from 'path'; | ||||||
| import { fileURLToPath } from 'url'; | ||||||
| import { createRequire as topLevelCreateRequire } from 'module'; | ||||||
| const require = topLevelCreateRequire(import.meta.url); | ||||||
| const __filename = fileURLToPath(import.meta.url); | ||||||
| const __dirname = path.dirname(__filename); | ||||||
| `.trim(), | ||||||
| }, // Banner for compatibility with CommonJS | ||||||
| }; | ||||||
|
|
||||||
| esbuild | ||||||
| .build({ | ||||||
| ...commonParams, | ||||||
| entryPoints: ["linkryEdgeFunction/index.js"], | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Incorrect entry point path. The entry point is set to After TypeScript compilation (assuming the tsconfig is fixed to include the current directory), the compiled output should be at Apply this diff to fix the entry point: - entryPoints: ["linkryEdgeFunction/index.js"],
+ entryPoints: ["../../dist/linkryEdgeFunction/index.js"],📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| outdir: "../../dist/linkryEdgeFunction/", | ||||||
| }) | ||||||
| .then(() => | ||||||
| console.log("Linkry Edge Function lambda build completed successfully!"), | ||||||
| ) | ||||||
| .catch((error) => { | ||||||
| console.error("Linkry Edge Function lambda build failed:", error); | ||||||
| process.exit(1); | ||||||
| }); | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,130 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DynamoDBClient, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QueryCommand, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QueryCommandInput, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@aws-sdk/client-dynamodb"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CloudFrontRequestEvent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CloudFrontRequestResult, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "aws-lambda"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const DEFAULT_AWS_REGION = "us-east-2"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const AVAILABLE_REPLICAS = ["us-west-2"]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const DYNAMODB_TABLE = "infra-core-api-linkry"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const FALLBACK_URL = process.env.FALLBACK_URL || "https://acm.illinois.edu/404"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const DEFAULT_URL = process.env.DEFAULT_URL || "https://www.acm.illinois.edu"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const CACHE_TTL = "30"; // seconds to hold response in PoP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Determine which DynamoDB replica to use based on Lambda execution region | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function selectReplica(lambdaRegion: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // First check if Lambda is running in a replica region | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (AVAILABLE_REPLICAS.includes(lambdaRegion)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return lambdaRegion; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Otherwise, find nearest replica by region prefix matching | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const regionPrefix = lambdaRegion.split("-").slice(0, 2).join("-"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (regionPrefix === "us") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return DEFAULT_AWS_REGION; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const replica of AVAILABLE_REPLICAS) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (replica.startsWith(regionPrefix)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return replica; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return DEFAULT_AWS_REGION; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+40
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logic error in region prefix matching. On line 28, Apply this diff to fix the logic: // Otherwise, find nearest replica by region prefix matching
const regionPrefix = lambdaRegion.split("-").slice(0, 2).join("-");
- if (regionPrefix === "us") {
+ if (regionPrefix.startsWith("us")) {
return DEFAULT_AWS_REGION;
}Alternatively, if the intent is to match US regions more broadly: - const regionPrefix = lambdaRegion.split("-").slice(0, 2).join("-");
- if (regionPrefix === "us") {
+ const regionPrefix = lambdaRegion.split("-")[0];
+ if (regionPrefix === "us") {
return DEFAULT_AWS_REGION;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentRegion = process.env.AWS_REGION || DEFAULT_AWS_REGION; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const targetRegion = selectReplica(currentRegion); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const dynamodb = new DynamoDBClient({ region: targetRegion }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Lambda in ${currentRegion}, routing DynamoDB to ${targetRegion}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const handler = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event: CloudFrontRequestEvent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<CloudFrontRequestResult> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const request = event.Records[0].cf.request; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const path = request.uri.replace(/^\/+/, ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Processing path: ${path}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!path) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "301", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| statusDescription: "Moved Permanently", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location: [{ key: "Location", value: DEFAULT_URL }], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "cache-control": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { key: "Cache-Control", value: `public, max-age=${CACHE_TTL}` }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Query DynamoDB for records with PK=path and SK starting with "OWNER#" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const queryParams: QueryCommandInput = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TableName: DYNAMODB_TABLE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| KeyConditionExpression: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "slug = :slug AND begins_with(access, :owner_prefix)", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExpressionAttributeValues: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ":slug": { S: path }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ":owner_prefix": { S: "OWNER#" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ProjectionExpression: "redirect", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Limit: 1, // We only need one result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await dynamodb.send(new QueryCommand(queryParams)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (response.Items && response.Items.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const item = response.Items[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Extract the redirect URL from the item | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const redirectUrl = item.redirect?.S; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (redirectUrl) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Found redirect: ${path} -> ${redirectUrl}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "302", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| statusDescription: "Found", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location: [{ key: "Location", value: redirectUrl }], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "cache-control": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { key: "Cache-Control", value: `public, max-age=${CACHE_TTL}` }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+69
to
+103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify DynamoDB attribute names and consider URL validation. The query uses Additionally, the redirect URL extracted from DynamoDB (line 89) is returned without validation. If an attacker could inject malicious URLs into the database, this could pose a security risk. Consider adding URL validation before redirecting: if (redirectUrl) {
// Validate URL format and protocol
try {
const url = new URL(redirectUrl);
if (!['http:', 'https:'].includes(url.protocol)) {
console.log(`Invalid protocol for redirect URL: ${redirectUrl}`);
// Fall through to fallback redirect
} else {
console.log(`Found redirect: ${path} -> ${redirectUrl}`);
return {
status: "302",
statusDescription: "Found",
headers: {
location: [{ key: "Location", value: redirectUrl }],
"cache-control": [
{ key: "Cache-Control", value: `public, max-age=${CACHE_TTL}` },
],
},
};
}
} catch (e) {
console.log(`Invalid URL format: ${redirectUrl}`);
// Fall through to fallback redirect
}
}Run the following script to verify the DynamoDB schema: 🏁 Script executed: #!/bin/bash
# Verify the DynamoDB table schema for infra-core-api-linkry
# Search for the table definition in Terraform or DynamoDB client code
rg -n "infra-core-api-linkry" -A 10 -B 2 --type=tf
# Search for any LinkryRecord or similar type definitions that show the schema
ast-grep --pattern 'interface $_ {
$$$
slug: $$$
$$$
}'
# Search for DynamoDB operations on the linkry table
rg -n "KeyConditionExpression|AttributeName" -A 5 -B 5 | rg -A 10 -B 10 "linkry"Length of output: 12034 Now I need to check if "slug" and "access" are specifically in the DynamoDB reserved words list. Let me search for that information. Use ExpressionAttributeNames for attribute names and validate redirect URLs. Two issues need attention:
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`Item found but no redirect attribute for path: ${path}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`No items found for path: ${path}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error instanceof Error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `DynamoDB query failed for ${path} in region ${targetRegion}:`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error.message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(`Unexpected error:`, error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Not found - redirect to fallback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: "307", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| statusDescription: "Temporary Redirect", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location: [{ key: "Location", value: FALLBACK_URL }], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "cache-control": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { key: "Cache-Control", value: `public, max-age=${CACHE_TTL}` }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix formatting issues.
The code has several formatting violations that should be addressed:
Run
prettier --write src/common/types/generic.tsto auto-fix these issues.🧰 Tools
🪛 ESLint
[error] 29-29: Insert
⏎···(prettier/prettier)
[error] 31-31: Insert
,(prettier/prettier)
[error] 32-32: Insert
;(prettier/prettier)
🤖 Prompt for AI Agents