Skip to content
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

Task 4 #193

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/bazel-out

# dependencies
/.serverless
/node_modules

# profiling files
Expand All @@ -21,6 +22,7 @@ speed-measure-plugin*.json
.c9/
*.launch
.settings/
package-lock.json
*.sublime-workspace

# IDE - VSCode
Expand Down
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"workbench.colorCustomizations": {
"tab.activeBorder": "#f89257",
"activityBar.background": "#183409",
"titleBar.activeBackground": "#21490C",
"titleBar.activeForeground": "#F5FCF0"
}
}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Cloudfront: -[CloudFrontLink](https://d2ee1433i4vg22.cloudfront.net/)
S3: -[s3Link](http://bookify-aws-app-lts.s3-website-us-east-1.amazonaws.com/)

# Shop Angular Cloudfront

Angular version: ~12.
Expand Down
8,793 changes: 7,824 additions & 969 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"@angular/router": "^15.1.4",
"bootstrap": "^4.6.0",
"rxjs": "^7.8.0",
"serverless": "^3.35.2",
"serverless-finch": "^4.0.3",
"serverless-single-page-app-plugin": "file:./serverless-single-page-app-plugin",
"tslib": "^2.5.0",
"zone.js": "^0.12.0"
},
Expand Down
151 changes: 151 additions & 0 deletions serverless-single-page-app-plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
'use strict';

const spawnSync = require('child_process').spawnSync;

class ServerlessPlugin {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.commands = {
syncToS3: {
usage: 'Deploys the `app` directory to your bucket',
lifecycleEvents: [
'sync',
],
},
domainInfo: {
usage: 'Fetches and prints out the deployed CloudFront domain names',
lifecycleEvents: [
'domainInfo',
],
},
invalidateCloudFrontCache: {
usage: 'Invalidates CloudFront cache',
lifecycleEvents: [
'invalidateCache',
],
},
};

this.hooks = {
'syncToS3:sync': this.syncDirectory.bind(this),
'domainInfo:domainInfo': this.domainInfo.bind(this),
'invalidateCloudFrontCache:invalidateCache': this.invalidateCache.bind(
this,
),
};
}

runAwsCommand(args) {
let command = 'aws';
if (this.serverless.variables.service.provider.region) {
command = `${command} --region ${this.serverless.variables.service.provider.region}`;
}
if (this.serverless.variables.service.provider.profile) {
command = `${command} --profile ${this.serverless.variables.service.provider.profile}`;
}
const result = spawnSync(command, args);

const stdout = typeof result.stdout === 'string' ? result.stdout : result.stdout && result.stdout.toString() ;
const sterr = typeof result.stderr === 'string' ? result.stderr : result.stderr && result.stderr.toString();
if (stdout) {
this.serverless.cli.log(stdout);
}
if (sterr) {
this.serverless.cli.log(sterr);
}

return { stdout, sterr };
}

// syncs the `app` directory to the provided bucket
syncDirectory() {
const s3Bucket = this.serverless.variables.service.custom.s3Bucket;
const args = [
's3',
'sync',
'app/',
`s3://${s3Bucket}/`,
'--delete',
];
const { sterr } = this.runAwsCommand(args);
if (!sterr) {
this.serverless.cli.log('Successfully synced to the S3 bucket');
} else {
throw new Error('Failed syncing to the S3 bucket');
}
}

// fetches the domain name from the CloudFront outputs and prints it out
async domainInfo() {
const provider = this.serverless.getProvider('aws');
const stackName = provider.naming.getStackName(this.options.stage);
const result = await provider.request(
'CloudFormation',
'describeStacks',
{ StackName: stackName },
this.options.stage,
this.options.region,
);

const outputs = result.Stacks[0].Outputs;
const output = outputs.find(
entry => entry.OutputKey === 'WebAppCloudFrontDistributionOutput',
);

if (output && output.OutputValue) {
this.serverless.cli.log(`Web App Domain: ${output.OutputValue}`);
return output.OutputValue;
}

this.serverless.cli.log('Web App Domain: Not Found');
const error = new Error('Could not extract Web App Domain');
throw error;
}

async invalidateCache() {
const provider = this.serverless.getProvider('aws');

const domain = await this.domainInfo();

const result = await provider.request(
'CloudFront',
'listDistributions',
{},
this.options.stage,
this.options.region,
);

const distributions = result.DistributionList.Items;
const distribution = distributions.find(
entry => entry.DomainName === domain,
);

if (distribution) {
this.serverless.cli.log(
`Invalidating CloudFront distribution with id: ${distribution.Id}`,
);
const args = [
'cloudfront',
'create-invalidation',
'--distribution-id',
distribution.Id,
'--paths',
'/*',
];
const { sterr } = this.runAwsCommand(args);
if (!sterr) {
this.serverless.cli.log('Successfully invalidated CloudFront cache');
} else {
throw new Error('Failed invalidating CloudFront cache');
}
} else {
const message = `Could not find distribution with domain ${domain}`;
const error = new Error(message);
this.serverless.cli.log(message);
throw error;
}
}
}

module.exports = ServerlessPlugin;
7 changes: 7 additions & 0 deletions serverless-single-page-app-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "serverless-single-page-app-plugin",
"version": "1.0.1",
"description": "A plugin to simplify deploying Single Page Application using S3 and CloudFront",
"author": "",
"license": "MIT"
}
134 changes: 134 additions & 0 deletions serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
org: berkin
app: bookify-aws-app
service: bookify-aws-app
frameworkVersion: "3"

provider:
name: aws
runtime: nodejs18.x

profile: aws-admin

plugins:
- serverless-finch
- serverless-single-page-app-plugin

custom:
client:
bucketName: bookify-aws-app-lts
distributionFolder: dist

## Serverless-single-page-app-plugin configuration:
s3LocalPath: ${self:custom.client.distributionFolder}/
s3BucketName: ${self:custom.client.bucketName}

# you can add CloudFormation resource templates here
resources:
Resources:
## Specifying the S3 Bucket
WebAppS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.s3BucketName}
PublicAccessBlockConfiguration:
BlockPublicAcls: false
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: index.html
# VersioningConfiguration:
# Status: Enabled

## Specifying the policies to make sure all files inside the Bucket are avaialble to CloudFront
WebAppS3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: WebAppS3Bucket
PolicyDocument:
Statement:
- Sid: "AllowCloudFrontServicePrincipal"
Effect: Allow
Action: s3:GetObject
Resource: arn:aws:s3:::${self:custom.s3BucketName}/*
Principal:
Service: "cloudfront.amazonaws.com"
Condition:
StringEquals:
AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${WebAppCloudFrontDistribution}

OriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Description: Default Origin Access Control
Name: ${self:custom.s3BucketName}.s3.amazonaws.com
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4

## Specifying the CloudFront Distribution to server your Web Application
WebAppCloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: ${self:custom.s3BucketName}.s3.amazonaws.com
## An identifier for the origin which must be unique within the distribution
Id: myS3Origin
## In case you don't want to restrict the bucket access use CustomOriginConfig and remove S3OriginConfig
S3OriginConfig:
OriginAccessIdentity: ""
OriginAccessControlId: !GetAtt OriginAccessControl.Id
# CustomOriginConfig:
# HTTPPort: 80
# HTTPSPort: 443
# OriginProtocolPolicy: https-only
Enabled: true
IPV6Enabled: true
HttpVersion: http2
## Uncomment the following section in case you are using a custom domain
# Aliases:
# - mysite.example.com
DefaultRootObject: index.html
## Since the Single Page App is taking care of the routing we need to make sure ever path is served with index.html
## The only exception are files that actually exist e.h. app.js, reset.css
CustomErrorResponses:
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
DefaultCacheBehavior:
AllowedMethods: ["GET", "HEAD", "OPTIONS"]
CachedMethods: ["GET", "HEAD", "OPTIONS"]
ForwardedValues:
Headers:
- Access-Control-Request-Headers
- Access-Control-Request-Method
- Origin
- Authorization
## Defining if and how the QueryString and Cookies are forwarded to the origin which in this case is S3
QueryString: false
Cookies:
Forward: none
## The origin id defined above
TargetOriginId: myS3Origin
## The protocol that users can use to access the files in the origin. To allow HTTP use `allow-all`
ViewerProtocolPolicy: redirect-to-https
Compress: true
DefaultTTL: 0
## The certificate to use when viewers use HTTPS to request objects.
ViewerCertificate:
CloudFrontDefaultCertificate: "true"
## Uncomment the following section in case you want to enable logging for CloudFront requests
# Logging:
# IncludeCookies: 'false'
# Bucket: mylogs.s3.amazonaws.com
# Prefix: myprefix

## In order to print out the hosted domain via `serverless info` we need to define the DomainName output for CloudFormation
Outputs:
WebAppS3BucketOutput:
Value:
"Ref": WebAppS3Bucket
WebAppCloudFrontDistributionOutput:
Value:
"Fn::GetAtt": [WebAppCloudFrontDistribution, DomainName]
7 changes: 2 additions & 5 deletions src/app/admin/manage-products/manage-products.component.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<h1>Manage products</h1>

<div class="d-flex">
<app-file-picker
class="mr-2"
[(file)]="selectedFile"
(uploadClick)="onUploadCSV()"
></app-file-picker>

<button
class="text-uppercase"
color="primary"
Expand All @@ -16,7 +14,6 @@ <h1>Manage products</h1>
create product
</button>
</div>

<table class="w-100" [dataSource]="products$" mat-table>
<ng-container matColumnDef="from">
<th mat-header-cell *matHeaderCellDef>Title</th>
Expand All @@ -31,7 +28,8 @@ <h1>Manage products</h1>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef>Price</th>
<td mat-cell *matCellDef="let product">
{{ product.price | number: "1.2-2" | currency }}
{{ product.price | number : "1.2-2" | currency }}
{{ product.price | currency }}
</td>
</ng-container>
<ng-container matColumnDef="count">
Expand All @@ -54,7 +52,6 @@ <h1>Manage products</h1>
</button>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="columns"></tr>
<tr mat-row *matRowDef="let row; columns: columns"></tr>
</table>
2 changes: 1 addition & 1 deletion src/app/cart/order-summary/order-summary.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ <h3 class="col flex-grow-1">Shipping</h3>
<div class="row">
<h3 class="col flex-grow-1">Total</h3>
<b class="col flex-grow-0" style="font-size: 18px">{{
totalPrice | number: "1.2-2" | currency
totalPrice | currency
}}</b>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ <h3 class="font-weight-bold mb-1">{{ product.title }}</h3>
<p class="text-muted">{{ product.description }}</p>
</div>
<div class="col-12 col-md-3">
{{ product.price | number: "1.2-2" | currency }} x
{{ product.orderedCount }} =
{{ product.totalPrice | number: "1.2-2" | currency }}
{{ product.price | currency }} x {{ product.orderedCount }} =
{{ product.totalPrice | currency }}
</div>
</div>
</mat-card>
Loading