Skip to content

Commit

Permalink
Migrate to SST v3 (#523)
Browse files Browse the repository at this point in the history
  • Loading branch information
js0mmer authored Dec 4, 2024
1 parent 6a56f3d commit 8511c6a
Show file tree
Hide file tree
Showing 8 changed files with 534 additions and 4,381 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/clean-up-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ jobs:
HUSKY: 0

- name: Remove staging stack
# need to clean up frontend stack first then backend stack or else backend stack doesn't get removed
run: pnpm sst remove --stage staging-${{ github.event.pull_request.number }} frontend && pnpm sst remove --stage staging-${{ github.event.pull_request.number }} backend
run: pnpm sst remove --stage staging-${{ github.event.pull_request.number }}
env:
CI: false
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ node_modules/
# sst files
.sst
cdk.context.json
sst-env.d.ts

# IDE settings
.idea
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Features include:
- Express
- React
- tRPC
- SST and AWS CDK
- SST
- PostgreSQL
- Drizzle ORM
- TypeScript
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
"db:studio": "pnpm --filter=api db:studio"
},
"dependencies": {
"aws-cdk-lib": "2.132.1",
"dotenv-flow": "^4.0.1",
"sst": "2.41.5"
"sst": "3.3.22"
},
"engines": {
"node": "^18 || ^20 || ^22",
"pnpm": "^9"
},
"devDependencies": {
"@types/aws-lambda": "8.10.145",
"@types/node": "20.12.8",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
Expand Down
4,626 changes: 389 additions & 4,237 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

157 changes: 140 additions & 17 deletions sst.config.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,148 @@
import { FrontendStack } from './stacks/frontend';
import { BackendStack } from './stacks/backend';
import dotenv from 'dotenv-flow';
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="./.sst/platform/config.d.ts" />

import { App } from 'sst/constructs';
function getDomainConfig() {
let domainName: string;
let domainRedirects: string[] | undefined;
if ($app.stage === 'prod') {
domainName = 'peterportal.org';
domainRedirects = ['www.peterportal.org'];
} else if ($app.stage === 'dev') {
domainName = 'dev.peterportal.org';
} else if ($app.stage.match(/^staging-(\d+)$/)) {
// check if stage is like staging-###
domainName = `${$app.stage}.peterportal.org`;
} else {
throw new Error('Invalid stage');
}
return { domainName, domainRedirects };
}

dotenv.config();
function createLambdaFunction() {
const environment = {
DATABASE_URL: process.env.DATABASE_URL!,
SESSION_SECRET: process.env.SESSION_SECRET!,
PUBLIC_API_URL: process.env.PUBLIC_API_URL!,
GOOGLE_CLIENT: process.env.GOOGLE_CLIENT!,
GOOGLE_SECRET: process.env.GOOGLE_SECRET!,
GRECAPTCHA_SECRET: process.env.GRECAPTCHA_SECRET!,
PRODUCTION_DOMAIN: process.env.PRODUCTION_DOMAIN!,
ADMIN_EMAILS: process.env.ADMIN_EMAILS!,
NODE_ENV: process.env.NODE_ENV ?? 'staging',
ANTEATER_API_KEY: process.env.ANTEATER_API_KEY!,
};

export default {
config() {
return new sst.aws.Function('PeterPortal Backend', {
handler: 'api/src/app.handler',
memory: '256 MB',
runtime: 'nodejs20.x',
logging: {
retention: $app.stage === 'prod' ? '2 years' : '1 week',
},
environment,
url: true,
});
}

/**
* forwards host since lambda function url overwrites host (x-forwarded-host is recovered in api/app.ts)
* encodes querystryings since cloudfront can't support it otherwise
* @returns cloudfront function
*/
const createCloudFrontInjectionFunction = () =>
new aws.cloudfront.Function('CloudFrontFunction', {
runtime: 'cloudfront-js-2.0',
// this code is copy/pasted from an SST sveltekit component, forwards host and encodes query string
code: `
function handler(event) {
var request = event.request;
request.headers["x-forwarded-host"] = request.headers.host;
for (var key in request.querystring) {
if (key.includes("/")) {
request.querystring[encodeURIComponent(key)] = request.querystring[key];
delete request.querystring[key];
}
}
return request;
}
`,
});

function createApiOrigin(lambdaFunction: sst.aws.Function): aws.types.input.cloudfront.DistributionOrigin {
return {
domainName: lambdaFunction.url.apply((url) => new URL(url).hostname),
originId: 'api',
customOriginConfig: {
httpPort: 80,
httpsPort: 443,
originProtocolPolicy: 'https-only',
originSslProtocols: ['TLSv1.2'],
},
};
}

function createStaticSite(
domainName: string,
domainRedirects: string[] | undefined,
apiOrigin: aws.types.input.cloudfront.DistributionOrigin,
cloudfrontInjectionFunction: aws.cloudfront.Function,
) {
return new sst.aws.StaticSite('PeterPortal Site', {
domain: {
name: domainName,
redirects: domainRedirects,
},
path: './site',
build: {
command: 'pnpm build',
output: 'dist',
},
transform: {
cdn: (args) => {
args.origins = $output(args.origins).apply((origins) => [...origins, apiOrigin]);
args.orderedCacheBehaviors = [
{
pathPattern: '/api/*',
allowedMethods: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE'],
cachedMethods: ['GET', 'HEAD'],
targetOriginId: apiOrigin.originId,
viewerProtocolPolicy: 'https-only',
cachePolicyId: '4135ea2d-6df8-44a3-9df3-4b5a84be39ad', // caching disabled policy: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html
originRequestPolicyId: 'b689b0a8-53d0-40ab-baf2-68738e2966ac', // all viewer except host header policy: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html
functionAssociations: [
{
eventType: 'viewer-request',
functionArn: cloudfrontInjectionFunction.arn,
},
],
},
];
},
},
});
}

export default $config({
app(input) {
return {
name: 'peterportal-client',
region: 'us-west-1',
removal: input?.stage === 'prod' ? 'retain' : 'remove',
home: 'aws',
providers: {
aws: {
region: 'us-west-1',
},
},
};
},
stacks(app: App) {
if (app.stage !== 'prod') {
app.setDefaultRemovalPolicy('destroy');
}

app
.stack(BackendStack, { stackName: `${app.name}-${app.stage}-backend` })
.stack(FrontendStack, { stackName: `${app.name}-${app.stage}-frontend` });

async run() {
const { domainName, domainRedirects } = getDomainConfig();
const lambdaFunction = createLambdaFunction();

const apiOrigin = createApiOrigin(lambdaFunction);
const cloudfrontInjectionFunction = createCloudFrontInjectionFunction();

createStaticSite(domainName, domainRedirects, apiOrigin, cloudfrontInjectionFunction);
},
};
});
31 changes: 0 additions & 31 deletions stacks/backend.ts

This file was deleted.

91 changes: 0 additions & 91 deletions stacks/frontend.ts

This file was deleted.

0 comments on commit 8511c6a

Please sign in to comment.