Skip to content

Instantly share code, notes, and snippets.

@ManotLuijiu
Last active September 19, 2024 06:35
Show Gist options
  • Save ManotLuijiu/1ec5237965f9d50faa9659f8715e0e8d to your computer and use it in GitHub Desktop.
Save ManotLuijiu/1ec5237965f9d50faa9659f8715e0e8d to your computer and use it in GitHub Desktop.
Migrate SST from V2 to V3 on Next.js app router project
// aws-lambda/card-image.ts
import { APIGatewayProxyHandlerV2 } from 'aws-lambda';
import { generateCardSvg } from '@/components/Dashboard/CardImage/GenerateCardSvg';
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
const { lastFourDigits, cardType } = event.queryStringParameters || {};
if (!lastFourDigits || !cardType) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Missing required parameters' }),
};
}
const svg = generateCardSvg({ lastFourDigits, cardType });
return {
statusCode: 200,
headers: {
'Content-Type': 'image/svg+xml',
},
body: svg,
};
};
// // @/components/Dashboard/CardImage/CardImage.tsx
interface CardImageProps {
lastFourDigits: string;
cardType: string;
}
export default function CardImage({ lastFourDigits, cardType }: CardImageProps) {
const apiUrl = process.env.NEXT_PUBLIC_API_URL; // Set this in your environment variables
const imageUrl = `${apiUrl}/api/card-image?lastFourDigits=${lastFourDigits}&cardType=${cardType}`;
return (
<div className="w-[300px] h-[180px]">
<img src={imageUrl} alt={`${cardType} card ending in ${lastFourDigits}`} className="w-full h-full object-cover rounded-lg" />
</div>
);
}
// @/components/Dashboard/CardImage/GenerateCardSvg.ts
interface CardDetails {
lastFourDigits: string;
cardType: string;
}
export function generateCardSvg({ lastFourDigits, cardType }: CardDetails): string {
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="180" viewBox="0 0 300 180">
<rect width="100%" height="100%" fill="#f0f0f0" rx="10" ry="10"/>
<text x="20" y="40" font-family="Arial, sans-serif" font-size="18" fill="#333333">${cardType}</text>
<text x="20" y="140" font-family="Arial, sans-serif" font-size="24" fill="#333333">**** **** **** ${lastFourDigits}</text>
</svg>
`;
return svg.trim();
}
// Create folder aws-lambda inside src folder,
// If you do not use src folder just put in root directory
// If so do not forget to modify sst.config.ts change handler: 'src/aws-lambda/sender.handler', to handler: 'aws-lambda/sender.handler',
// src/aws-lambda/route.ts
import { APIGatewayProxyEventV2 } from 'aws-lambda';
export const handler = async (event: APIGatewayProxyEventV2) => {
console.log('event', event);
return {
statusCode: 200,
body: JSON.stringify({ route: event.routeKey, status: 'ok' }, null, 2),
};
};
// Create folder aws-lambda inside src folder,
// If you do not use src folder just put in root directory
// If so do not forget to modify sst.config.ts change handler: 'src/aws-lambda/sender.handler', to handler: 'aws-lambda/sender.handler',
// src/aws-lambda/sender.ts
import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';
import { Resource } from 'sst';
const client = new SESv2Client({});
export const handler = async () => {
await client.send(
new SendEmailCommand({
FromEmailAddress: Resource.YourAppEmail.sender,
Destination: {
ToAddresses: ['[email protected]'],
},
Content: {
Simple: {
Subject: {
Data: 'Test AWS SES send by AWS Lambda',
},
Body: {
Text: {
Data: 'Send from my SST app',
},
},
},
},
}),
);
};
/// <reference path="./.sst/platform/config.d.ts" />
export default $config({
app(input) {
return {
name: 'your-app-name',
removal: input?.stage === 'production' ? 'retain' : 'remove',
home: 'aws',
providers: {
aws: {
region: 'ap-southeast-1',
},
},
};
},
async run() {
const email = new sst.aws.Email('YourAppEmail', {
sender: $app.stage === 'production' ? '[email protected]' : `${$app.stage}@your-domain.com`,
});
const emailApi = new sst.aws.Function('YourAppEmailApi', {
handler: 'src/aws-lambda/sender.handler',
link: [email],
url: true,
});
const api = new sst.aws.ApiGatewayV2('YourAppApi', {
domain: {
name: $app.stage === 'production' ? 'api.your-domain.com' : `${$app.stage}.your-domain.com`,
path: 'v1',
},
});
api.route('GET /', {
handler: 'src/aws-lambda/route.handler',
});
api.route('GET /foo', 'src/aws-lambda/route.handler', { auth: { iam: true } });
// Generate dynamic image
api.route('GET /card-image', {
handler: 'src/aws-lambda/card-image.handler',
});
const bucket = new sst.aws.Bucket('YourAppBucket', {
cors: {
allowHeaders: ['Authorization', 'Content-Type'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD'],
allowOrigins: ['https://www.your-domain.com', 'https://staging.your-domain.com', 'http://localhost:3000'],
},
});
const openai = new sst.Secret('OpenAiApiKey');
const stripePublishKey = new sst.Secret('StripePublishKey');
const stripeSecret = new sst.Secret('StripeSecretKey');
const appUrl = new sst.Secret('AppUrl');
const databaseUrl = new sst.Secret('DatabaseUrl');
const directUrl = new sst.Secret('DirectUrl');
const frontEndUrl = new sst.Secret('FrontEndUrl');
const clerkPublishableKey = new sst.Secret('ClerkPublishableKey');
const clerkSecretKey = new sst.Secret('ClerkSecretKey');
const clerkWebhookSecret = new sst.Secret('ClerkWebhookSecret');
new sst.aws.Nextjs('YourAppSAASWeb', {
link: [
bucket,
openai,
stripePublishKey,
stripeSecret,
appUrl,
frontEndUrl,
databaseUrl,
directUrl,
clerkPublishableKey,
clerkSecretKey,
clerkWebhookSecret
],
environment:
$app.stage === 'production'
? {
OPENAI_API_KEY: openai.value,
NEXT_PUBLIC_STRIPE_PUBLISH_KEY: stripePublishKey.value,
STRIPE_SECRET_KEY: stripeSecret.value,
NEXT_PUBLIC_APP_URL: appUrl.value,
DATABASE_URL: databaseUrl.value,
DIRECT_URL: directUrl.value,
FRONTEND_URL: frontEndUrl.value,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: clerkPublishableKey.value,
CLERK_SECRET_KEY: clerkSecretKey.value,
CLERK_WEBHOOK_SECRET: clerkWebhookSecret.value,
}
: {
OPENAI_API_KEY: openai.value,
NEXT_PUBLIC_STRIPE_PUBLISH_KEY: stripePublishKey.value,
STRIPE_SECRET_KEY: stripeSecret.value,
NEXT_PUBLIC_APP_URL: appUrl.value,
DATABASE_URL: databaseUrl.value,
DIRECT_URL: directUrl.value,
FRONTEND_URL: frontEndUrl.value,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: clerkPublishableKey.value,
CLERK_SECRET_KEY: clerkSecretKey.value,
CLERK_WEBHOOK_SECRET: clerkWebhookSecret.value,
},
domain: {
name:
// Redirect your-domain.com to www.your-domain.com
$app.stage === 'production'
? 'www.your-domain.com'
: `${$app.stage}.your-domain.com`,
redirects: $app.stage === 'production' ? ['your-domain.com'] : undefined,
},
});
return {
Bucket: bucket.name,
Api: api.url,
EmailUrl: emailApi.url,
};
},
});
@ManotLuijiu
Copy link
Author

ManotLuijiu commented Sep 2, 2024

Then you can put your secret value in the terminal using a command like this.

Case: Development(http://localhost:3000 or https://your-user-name.your-domain.com)
npx sst secret set StripeSecretWebhook whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Case: Staging(https://staging.your-domain.com)
npx sst secret set StripeSecretWebhook whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --stage staging

Case: Production(https://www.your-domain.com)
npx sst secret set StripeSecretWebhook whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --stage production

And run this command over again for every secret.
If you want to see all the secrets for each stage use this command

Case: Development(http://localhost:3000 or https://your-user-name.your-domain.com)
npx sst secret list

Case: Staging(https://staging.your-domain.com)
npx sst secret list --stage staging

Case: Production(https://www.your-domain.com)
npx sst secret list --stage production

Once ready, you can use this secret everywhere on the Next.js project using this import
import { Resource } from 'sst';

Remark:

  • Please note that you need to use Resource under npx sst dev, the next dev command does not get access to Resource from sst.
  • If you have doubts about what is inside Resource click at sst.
  • Before seeing the values in sst's Resource, you must run npx sst dev or npx sst deploy.

Add dynamic image to AWS Lambda

  1. Card SVG file generateCardSvg.ts
  2. Create API card-image.ts
  3. Edit sst.config.ts see code at line number 37 - 39 sst.config.ts
  4. Create CardImage Component CardImage.tsx

Reference: https://v0.dev/chat/-NYYJGoTEzj?b=b_u33Pfd3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment