Skip to content

Commit

Permalink
feat(cdk): added cdk code to deploy static Next.js website to cloudfr…
Browse files Browse the repository at this point in the history
…ont + s3. (#37)

* revert(cdk): removed Apple OAuth.
* feat(webapp): static export for next.
* feat: added .eslintignore to speed up lints.
* ci: added clean scripts to package.json.
* feat(cdk): added cdk code to deploy static Next.js website to cloudfront + s3.
  • Loading branch information
hwelsters authored Dec 24, 2023
1 parent 08978dc commit 94e79d3
Show file tree
Hide file tree
Showing 24 changed files with 517 additions and 62 deletions.
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ GOOGLE_CLIENT_SECRET=[YOUR GOOGLE CLIENT SECRET]

FACEBOOK_CLIENT_ID=[YOUR FACEBOOK CLIENT ID]
FACEBOOK_CLIENT_SECRET=[YOUR FACEBOOK CLIENT SECRET]

APPLE_CLIENT_ID=[YOUR APPLE CLIENT ID]
APPLE_CLIENT_SECRET=[YOUR APPLE CLIENT SECRET]
APPLE_KEY_ID=[YOUR APPLE KEY ID]
APPLE_PRIVATE_KEY=[YOUR APPLE PRIVATE KEY]
APPLE_TEAM_ID=[YOUR APPLE TEAM ID]
```

To get these values, create an OAuth 2.0 Client ID in your Google Developer Console.
2 changes: 2 additions & 0 deletions apps/snakecode-cdk/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/cdk.out
/node_modules
1 change: 0 additions & 1 deletion apps/snakecode-cdk/lib/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Environment } from "aws-cdk-lib";
import { App } from "aws-cdk-lib";

import { INFRA_CONFIG } from "@snakecode/models";

import { StageUtils } from "./utils/StageUtils";

const app = new App();
Expand Down
6 changes: 0 additions & 6 deletions apps/snakecode-cdk/lib/constants/Env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ const Env = cleanEnv(process.env, {

FACEBOOK_CLIENT_ID: str(),
FACEBOOK_CLIENT_SECRET: str(),

APPLE_CLIENT_ID: str(),
APPLE_CLIENT_SECRET: str(),
APPLE_KEY_ID: str(),
APPLE_PRIVATE_KEY: str(),
APPLE_TEAM_ID: str(),
});

export default Env;
14 changes: 0 additions & 14 deletions apps/snakecode-cdk/lib/stacks/AmplifyAuthStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
UserPool,
UserPoolClient,
UserPoolClientIdentityProvider,
UserPoolIdentityProviderApple,
UserPoolIdentityProviderFacebook,
UserPoolIdentityProviderGoogle,
VerificationEmailStyle,
Expand All @@ -18,7 +17,6 @@ import type { Construct } from "constructs";

import type { AmplifyAuthConfiguration } from "@snakecode/models";
import { APP_NAME, BASE_URL, ENVIRONMENT_NAME } from "@snakecode/models";

import Env from "../constants/Env";

export class AmplifyAuthStack extends NestedStack {
Expand Down Expand Up @@ -109,18 +107,6 @@ export class AmplifyAuthStack extends NestedStack {
},
});

new UserPoolIdentityProviderApple(this, `${props.amplifyAuthConfiguration.userPoolIdentityProviderAppleName}-${props.stage}-${props.env!.region}`, {
userPool,
clientId: Env.APPLE_CLIENT_ID,
keyId: Env.APPLE_KEY_ID,
privateKey: Env.APPLE_PRIVATE_KEY,
teamId: Env.APPLE_TEAM_ID,
scopes: ["email"],
attributeMapping: {
email: ProviderAttribute.APPLE_EMAIL,
},
});

// This user pool client will be used by the Amplify frontend
const cognitoUserPoolClient = new UserPoolClient(this, `${props.amplifyAuthConfiguration.userPoolClientName}-${props.stage}-${props.env!.region}`, {
userPool,
Expand Down
1 change: 0 additions & 1 deletion apps/snakecode-cdk/lib/stacks/AmplifyStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { Construct } from "constructs";

import type { AmplifyConfiguration } from "@snakecode/models";
import { APP_NAME, Constants } from "@snakecode/models";

import { AmplifyAuthStack } from "./AmplifyAuthStack";

export class AmplifyStack extends Stack {
Expand Down
46 changes: 46 additions & 0 deletions apps/snakecode-cdk/lib/stacks/StaticWebsiteHostingStack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { StackProps } from "aws-cdk-lib";
import { aws_s3, CfnOutput, RemovalPolicy, Stack } from "aws-cdk-lib";
import { Distribution, OriginAccessIdentity } from "aws-cdk-lib/aws-cloudfront";
import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
import { BucketAccessControl } from "aws-cdk-lib/aws-s3";
import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment";
import type { Construct } from "constructs";

import type { StaticWebsiteHostingStackConfiguration } from "@snakecode/models";

export class StaticWebsiteHostingStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps & { staticWebsiteHostingStackConfiguration: StaticWebsiteHostingStackConfiguration; stage: string; staticAssetsFilePath: string; cfnOutputName: string }) {
super(scope, id, props);

const bucket = new aws_s3.Bucket(this, `${props.staticWebsiteHostingStackConfiguration.bucketName}-${props.stage}-${props.env!.region}`, {
accessControl: BucketAccessControl.PRIVATE,

// Ensures that the S3 bucket is properly cleaned up when the stack is deleted.
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});

// TODO: Path is baked in at the moment
// Is there some way to verify that this path exists?
// Deploy the static website to the bucket.
new BucketDeployment(this, `${props.staticWebsiteHostingStackConfiguration.bucketDeploymentName}-${props.stage}-${props.env!.region}`, {
destinationBucket: bucket,
sources: [Source.asset(props.staticAssetsFilePath)],
});

const originAccessIdentity = new OriginAccessIdentity(this, `${props.staticWebsiteHostingStackConfiguration.originAccessIdentityName}-${props.stage}-${props.env!.region}`);
bucket.grantRead(originAccessIdentity);

const distribution = new Distribution(this, `${props.staticWebsiteHostingStackConfiguration.distributionName}`, {
defaultRootObject: "index.html",
defaultBehavior: {
origin: new S3Origin(bucket, { originAccessIdentity }),
},
});

new CfnOutput(this, props.cfnOutputName, {
exportName: props.cfnOutputName.replaceAll("_", "-"),
value: distribution.domainName,
});
}
}
14 changes: 12 additions & 2 deletions apps/snakecode-cdk/lib/utils/StageUtils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { App, Environment } from "aws-cdk-lib";
import path from "node:path";

import type { StageConfiguration } from "@snakecode/models";
import type { App, Environment } from "aws-cdk-lib";

import { Constants, type StageConfiguration } from "@snakecode/models";
import { AmplifyStack } from "../stacks/AmplifyStack";
import { StaticWebsiteHostingStack } from "../stacks/StaticWebsiteHostingStack";

export class StageUtils {
private readonly configuration: StageConfiguration;
Expand All @@ -26,5 +28,13 @@ export class StageUtils {
stage: this.stage,
amplifyStackConfiguration: this.configuration.amplifyStackConfiguration,
});

new StaticWebsiteHostingStack(this.app, this.configuration.staticWebsiteHostingStackConfiguration.stackName, {
env: this.env,
stage: this.stage,
staticWebsiteHostingStackConfiguration: this.configuration.staticWebsiteHostingStackConfiguration,
staticAssetsFilePath: path.resolve(__dirname, "../../../../apps/snakecode-webapp/out"),
cfnOutputName: Constants.StaticWebsiteHostingConstants.MAIN_WEBSITE_DOMAIN_NAME,
});
};
}
2 changes: 2 additions & 0 deletions apps/snakecode-cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build": "tsc",
"lint": "eslint . --fix",
"lint:staged": "lint-staged",
"clean": "rm -rf node_modules/",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk"
Expand All @@ -24,6 +25,7 @@
"typescript": "~5.1.6"
},
"dependencies": {
"@aws-cdk/aws-amplify": "^1.204.0",
"@snakecode/models": "1.0.0",
"aws-cdk-lib": "2.90.0",
"constructs": "^10.0.0",
Expand Down
3 changes: 3 additions & 0 deletions apps/snakecode-webapp/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/out
/.next
/node_modules
11 changes: 7 additions & 4 deletions apps/snakecode-webapp/exports/cdk-exports-dev.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
{
"snakecode-amplify-stack": {
"amplifyid": "d3thed3kk59i2n",
"amplifyid": "dme6lbx52jvft",
"userpoolsdomainurl": "snakecode-dev.auth.us-west-2.amazoncognito.com",
"identitypoolid": "us-west-2:db458b37-0e50-4284-beb1-75c1e9c72890",
"userpoolswebclientid": "3j97ggu8ccurvnnc4tkc3aua13",
"userpoolsid": "us-west-2_93GYjaMY6",
"identitypoolid": "us-west-2:24f4a730-f2d4-4533-a30b-273197201ea4",
"userpoolswebclientid": "4qtj9vn0fn6ovj0ohi40ojaco4",
"userpoolsid": "us-west-2_qKsYab2W1",
"region": "us-west-2",
"cognitoregion": "us-west-2"
},
"snakecode-nextjs-ssg-hosting-stack": {
"mainwebsitedomainname": "d14sb61hcjf629.cloudfront.net"
}
}
4 changes: 4 additions & 0 deletions apps/snakecode-webapp/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: "export",
images: {
unoptimized: true,
},
};

module.exports = nextConfig;
1 change: 1 addition & 0 deletions apps/snakecode-webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"clean": "rm -rf .next/ node_modules/ out/",
"lint": "eslint . --fix",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { useContext, useState } from "react";

import { python } from "@codemirror/lang-python";
import CodeMirror from "@uiw/react-codemirror";
import { useContext, useState } from "react";

import { PyodideContext } from "@/providers/pyodide-provider";

import SpeechBubble from "./speech-bubble";
import theme from "./theme";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useRouter } from "next/router";

import PlayArrowRoundedIcon from "@mui/icons-material/PlayArrowRounded";
import StarRoundedIcon from "@mui/icons-material/StarRounded";
import { useRouter } from "next/router";

import { addPathSegment } from "@/utils/addPathSegment";

Expand Down
1 change: 0 additions & 1 deletion apps/snakecode-webapp/src/pages/account/login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export default function Login() {
<span className="mb-8 flex w-full flex-col space-y-4">
<OAuthSignInButton logo={googleLogo} text="Google" color="bg-pc text-white" onClick={() => Auth.federatedSignIn({ provider: CognitoHostedUIIdentityProvider.Google })} />
<OAuthSignInButton logo={facebookLogo} text="Facebook" color="bg-[#4867aa] text-white" onClick={() => Auth.federatedSignIn({ provider: CognitoHostedUIIdentityProvider.Facebook })} />
<OAuthSignInButton logo={appleLogo} text="Apple" color="bg-black text-white" onClick={() => Auth.federatedSignIn({ provider: CognitoHostedUIIdentityProvider.Apple })} />
</span>
</span>
</main>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";
import { useState } from "react";

import CloseIcon from "@mui/icons-material/Close";
import KeyboardArrowLeftRoundedIcon from "@mui/icons-material/KeyboardArrowLeftRounded";
import KeyboardArrowRightRoundedIcon from "@mui/icons-material/KeyboardArrowRightRounded";
import type { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";
import { useState } from "react";

import Coding from "@/components/modules/courses/[course]/[section]/[lesson]/coding";
import Description from "@/components/modules/courses/[course]/[section]/[lesson]/description";
Expand Down
5 changes: 2 additions & 3 deletions packages/eslint-config-snakecode-base/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-unused-vars": "off",
"import/prefer-default-export": "off",
"import/extensions": "off",
"no-restricted-syntax": [
"error",
"ForInStatement",
Expand All @@ -48,10 +49,8 @@ module.exports = {
{
groups: [
["^node:"],
["^next", "^react"],
["^@?\\w"],
["^@snakecode", "^@", "^@public", "^@root"],
["^\\."],
["^@snakecode", "^@", "^@public", "^@root", "^\\."],
],
},
],
Expand Down
24 changes: 11 additions & 13 deletions packages/snakecode-commitlint/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
/** ******************************************************
Function used to export the CommitLint configuration.
******************************************************* */
export const setCommitLintConfig = () => {
return {
extends: ["@commitlint/config-conventional", "@commitlint/config-lerna-scopes"],
export const setCommitLintConfig = () => ({
extends: ["@commitlint/config-conventional", "@commitlint/config-lerna-scopes"],

// Any rules defined below here will override rules from @commitlint/config-conventional
rules: {
"type-enum": [2, "always", ["feat", "fix", "ci", "docs", "style", "refactor", "test", "revert", "lint"]],
"subject-empty": [2, "never"],
"subject-full-stop": [2, "always", "."],
"subject-max-length": [2, "always", 72],
"type-case": [2, "always", "lower-case"],
},
};
};
// Any rules defined below here will override rules from @commitlint/config-conventional
rules: {
"type-enum": [2, "always", ["feat", "fix", "ci", "docs", "style", "refactor", "test", "revert", "lint"]],
"subject-empty": [2, "never"],
"subject-full-stop": [2, "always", "."],
"subject-max-length": [2, "always", 72],
"type-case": [2, "always", "lower-case"],
},
});
4 changes: 4 additions & 0 deletions packages/snakecode-models/src/common/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ export namespace Constants {
export const USER_POOLS_WEB_CLIENT_ID: string = `user_pools_web_client_id`;
export const USER_POOLS_DOMAIN_URL: string = `user_pools_domain_url`;
}

export namespace StaticWebsiteHostingConstants {
export const MAIN_WEBSITE_DOMAIN_NAME: string = "main_website_domain_name";
}
}
8 changes: 8 additions & 0 deletions packages/snakecode-models/src/config/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,12 @@ export const INFRA_CONFIG: StageConfiguration = {
authenticatedRoleName: `${ENVIRONMENT_NAME}-authenticated-role`,
},
},

staticWebsiteHostingStackConfiguration: {
stackName: `${ENVIRONMENT_NAME}-nextjs-ssg-hosting-stack`,
bucketName: `${ENVIRONMENT_NAME}-nextjs-ssg-hosting-bucket`,
bucketDeploymentName: `${ENVIRONMENT_NAME}-nextjs-ssg-hosting-bucket-deployment`,
distributionName: `${ENVIRONMENT_NAME}-nextjs-ssg-hosting-distribution`,
originAccessIdentityName: `${ENVIRONMENT_NAME}-nextjs-ssg-hosting-origin-access-identity`,
},
};
8 changes: 8 additions & 0 deletions packages/snakecode-models/src/models/ServiceConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ export interface AmplifyAuthConfiguration {
readonly authenticatedRoleName: string;
readonly unauthenticatedRoleName: string;
}

export interface StaticWebsiteHostingStackConfiguration {
readonly stackName: string;
readonly bucketName: string;
readonly bucketDeploymentName: string;
readonly distributionName: string;
readonly originAccessIdentityName: string;
}
3 changes: 2 additions & 1 deletion packages/snakecode-models/src/models/StageConfiguration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AmplifyConfiguration } from "./ServiceConfiguration";
import type { AmplifyConfiguration, StaticWebsiteHostingStackConfiguration } from "./ServiceConfiguration";

export interface StageConfiguration {
readonly amplifyStackConfiguration: AmplifyConfiguration;
readonly staticWebsiteHostingStackConfiguration: StaticWebsiteHostingStackConfiguration;
}
Loading

0 comments on commit 94e79d3

Please sign in to comment.