Repository: evanchiu/serverless-galleria Branch: main Commit: cf8ca8854e79 Files: 60 Total size: 74.5 KB Directory structure: gitextract_jmmqs81b/ ├── .eslintrc.cjs ├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── blur/ │ ├── README.md │ ├── SAR_README.md │ ├── package.json │ ├── rollup.config.js │ ├── src/ │ │ └── index.js │ └── template.yml ├── compress/ │ ├── README.md │ ├── SAR_README.md │ ├── package.json │ ├── rollup.config.js │ ├── src/ │ │ └── index.js │ └── template.yml ├── crop/ │ ├── README.md │ ├── SAR_README.md │ ├── package.json │ ├── rollup.config.js │ ├── src/ │ │ └── index.js │ └── template.yml ├── galleria/ │ ├── README.md │ ├── SAR_README.md │ ├── package.json │ ├── public/ │ │ └── index.template.html │ ├── rollup.config.js │ ├── src/ │ │ └── index.js │ └── template.yml ├── package.json ├── resize/ │ ├── README.md │ ├── SAR_README.md │ ├── package.json │ ├── rollup.config.js │ ├── src/ │ │ └── index.js │ └── template.yml ├── rotate/ │ ├── README.md │ ├── SAR_README.md │ ├── package.json │ ├── rollup.config.js │ ├── src/ │ │ └── index.js │ └── template.yml ├── sepia/ │ ├── README.md │ ├── SAR_README.md │ ├── package.json │ ├── rollup.config.js │ ├── src/ │ │ └── index.js │ └── template.yml ├── serverless-galleria-util/ │ ├── index.js │ ├── package.json │ └── rollup.config.js └── uploader/ ├── .eslintrc.cjs ├── README.md ├── SAR_README.md ├── package.json ├── public/ │ └── index.html ├── rollup.config.js ├── src/ │ └── index.js └── template.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.cjs ================================================ module.exports = { "env": { "es2021": true, "node": true }, "extends": "eslint:recommended", "overrides": [ { "env": { "node": true }, "files": [ ".eslintrc.{js,cjs}" ], "parserOptions": { "sourceType": "script" } } ], "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "rules": { } } ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # package builds build bundle.js # CloudFormation packaged templates packaged-template.yml ================================================ FILE: .jshintrc ================================================ { "node": true, "esversion": 6 } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017-2023 Evan Chiu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # serverless-galleria Serverless batch photo manipulation and publishing ## Design ### Uploader The Uploader stores images in an S3 bucket. It doesn't own any buckets. ![uploader](diagrams/uploader.png) * [uploader](uploader) - Serverless web application for uploading images to S3 ### Transformations A transform owns an S3 bucket, which it watches for incoming files. When files are added, it runs a lambda function to transform the images and place them in another S3 bucket ![transform](diagrams/transform.png) * [blur](blur) - Apply a configurable blur * [compress](compress) - Apply image compression to reduce image file size * [crop](crop) - Apply a configurable crop * [resize](resize) - Apply a configurable resize * [rotate](rotate) - Apply a configurable rotation with configurable background color * [sepia](sepia) - Apply sepia tone ### Galleria The Galleria reads from two S3 buckets, one for reading image thumbnails, the other for full-size images. It doesn't own any buckets. ![galleria](diagrams/galleria.png) * [galleria](galleria) - Beautiful web interface for displaying photo gallery ## Setup First, plan your pipeline, as you'll build it backwards. Here's a sample: ![pipeline](diagrams/pipeline.png) ### Steps 1. Deploy Application 1. Create the thumbs bucket, as it's not owned by any transformations 1. Deploy the compress transform, with the resized bucket as its source, and thumbs bucket as its destination 1. Deploy the resize transform, with the rotated bucket as its source, and resized bucket as its destination 1. Deploy the rotate transform, with the originals bucket as its source, and rotated bucket as its destination 1. Deploy the uploader, with the originals bucket as its destination 1. Deploy the galleria, with originals as its full-size bucket, and thumbs as its thumb bucket 1. Upload photos 1. In the [API Gateway Console](https://console.aws.amazon.com/apigateway), navigate to APIs / uploader / Dashboard 1. Find the Invocation url, something like *https://xxxxxxxxx.execute-api.region.amazonaws.com/Prod/* 1. (You can also set up [custom domain name](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html)) 1. Open the invocation url in your browser, and drag photos on to the drop point to upload 1. View galleria 1. Set up a [custom domain name](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html) for the galleria API, then open it in your browser ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: blur/README.md ================================================ # blur Copy and apply a blur to images using Lambda. ## Deploy with CloudFormation Prerequisites: [Node.js](https://nodejs.org/en/) and [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) installed * Create an [AWS](https://aws.amazon.com/) Account and [IAM User](https://aws.amazon.com/iam/) with the `AdministratorAccess` AWS [Managed Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) * Run `aws configure` to put store that user's credentials in `~/.aws/credentials` * Create an S3 bucket for storing the Lambda code and store its name in a shell variable with: * `export CODE_BUCKET=bucket` * Create the S3 bucket for the blurred output, store its name in shell variable: * `export DEST_BUCKET=bucket` * Choose a name, but do NOT create the S3 bucket input comes from, store its name in shell variable: * `export SOURCE_BUCKET=bucket` * Choose the blur pixel radius, store it in shell variable: * `export BLUR_RADIUS=10` * Npm install: * `npm install` * Build: * `npm run build` * Upload package to S3, transform the CloudFormation template: * `npm run package` * Deploy to CloudFormation: * `npm run deploy` ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~blur) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [blur](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~blur) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: blur/SAR_README.md ================================================ # blur Serverless image blur This application is designed to be used as a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) transform, but it can also function as a generic JPEG blurer. ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Note that if you're using a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) image processing pipeline, this bucket will be created by the following transform, unless this is the last transform. * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~blur) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [blur](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~blur) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: blur/package.json ================================================ { "name": "blur", "version": "1.2.1", "description": "Blur transformation for Serverless Galleria", "type": "module", "main": "src/index.js", "scripts": { "lint": "eslint src", "build": "rollup --config", "package": "aws cloudformation package --template-file template.yml --output-template-file packaged-template.yml --s3-bucket $CODE_BUCKET", "deploy": "aws cloudformation deploy --template-file packaged-template.yml --capabilities CAPABILITY_IAM --stack-name dev-blur-$USER --parameter-overrides sourceBucket=$SOURCE_BUCKET destBucket=$DEST_BUCKET blurRadius=$BLUR_RADIUS" }, "repository": { "type": "git", "url": "git+https://github.com/evanchiu/serverless-galleria.git" }, "keywords": [ "Serverless", "ImageMagick", "Blur" ], "author": "Evan Chiu ", "license": "MIT", "bugs": { "url": "https://github.com/evanchiu/serverless-galleria/issues" }, "homepage": "https://github.com/evanchiu/serverless-galleria#readme", "dependencies": { "jimp": "^0.22.10", "serverless-galleria-util": "1.2.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "eslint": "^8.52.0", "rollup": "^4.1.4" } } ================================================ FILE: blur/rollup.config.js ================================================ import rollupConfig from "serverless-galleria-util/rollup.config.js"; export default rollupConfig; ================================================ FILE: blur/src/index.js ================================================ import Jimp from "jimp"; import { handle } from "serverless-galleria-util"; /** Handle the event from s3 */ export async function handler(event) { const blurRadius = parseInt(process.env.BLUR_RADIUS); if (typeof blurRadius !== "number") { throw new Error("Error: Environment variable BLUR_RADIUS missing"); } console.log(`Transforming with blur radius (${blurRadius})`); await handle(event, async (inBuffer) => { const image = await Jimp.read(inBuffer); image.blur(blurRadius); return image.getBufferAsync(Jimp.MIME_JPEG); }); } ================================================ FILE: blur/template.yml ================================================ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Transforms images by applying blur of a configured radius Resources: transform: Type: AWS::Serverless::Function Properties: Description: Transforms images by applying blur of a configured radius Handler: bundle.handler Runtime: nodejs18.x CodeUri: bundle.js MemorySize: 1536 Policies: - S3ReadPolicy: BucketName: Ref: sourceBucket - S3WritePolicy: BucketName: Ref: destBucket Timeout: 300 Events: upload: Type: S3 Properties: Bucket: Ref: source Events: s3:ObjectCreated:* Environment: Variables: DEST_BUCKET: Ref: destBucket BLUR_RADIUS: Ref: blurRadius NODE_OPTIONS: --enable-source-maps source: Type: AWS::S3::Bucket Properties: BucketName: Ref: sourceBucket Parameters: sourceBucket: Type: String Description: Name of the S3 Bucket to read source images from (must NOT exist prior to deployment) destBucket: Type: String Description: Name of the S3 Bucket to put transformed images into (must exist prior to deployment) blurRadius: Type: Number Description: Pixel radius of the blur to apply Default: 10 ================================================ FILE: compress/README.md ================================================ # compress Copy and compress images using Lambda. ## Deploy with CloudFormation Prerequisites: [Node.js](https://nodejs.org/en/) and [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) installed * Create an [AWS](https://aws.amazon.com/) Account and [IAM User](https://aws.amazon.com/iam/) with the `AdministratorAccess` AWS [Managed Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) * Run `aws configure` to put store that user's credentials in `~/.aws/credentials` * Create an S3 bucket for storing the Lambda code and store its name in a shell variable with: * `export CODE_BUCKET=bucket` * Create the S3 bucket for the compressed output, store its name in shell variable: * `export DEST_BUCKET=bucket` * Choose a name, but do NOT create the S3 bucket input comes from, store its name in shell variable: * `export SOURCE_BUCKET=bucket` * Choose the JPEG compression quality, 1 to 100, 100 is best quality/largest file, (See [docs](http://www.graphicsmagick.org/GraphicsMagick.html#details-quality)), store it in shell variable: * `export QUALITY=25` * Npm install: * `npm install` * Build: * `npm run build` * Upload package to S3, transform the CloudFormation template: * `npm run package` * Deploy to CloudFormation: * `npm run deploy` ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~compress) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [compress](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~compress) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: compress/SAR_README.md ================================================ # compress Serverless image compression This application is designed to be used as a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) transform, but it can also function as a generic JPEG image compressor. ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Note that if you're using a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) image processing pipeline, this bucket will be created by the following transform, unless this is the last transform. * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~compress) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [compress](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~compress) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: compress/package.json ================================================ { "name": "compress", "version": "1.2.1", "description": "Copy and compress images using Lambda", "type": "module", "main": "src/index.js", "scripts": { "lint": "eslint src", "build": "rollup --config", "package": "aws cloudformation package --template-file template.yml --output-template-file packaged-template.yml --s3-bucket $CODE_BUCKET", "deploy": "aws cloudformation deploy --template-file packaged-template.yml --capabilities CAPABILITY_IAM --stack-name dev-compress-$USER --parameter-overrides sourceBucket=$SOURCE_BUCKET destBucket=$DEST_BUCKET quality=$QUALITY" }, "repository": { "type": "git", "url": "git+https://github.com/evanchiu/serverless-galleria.git" }, "keywords": [ "Compress", "Image", "ImageMagick", "S3", "Serverless" ], "author": "Evan Chiu ", "license": "MIT", "bugs": { "url": "https://github.com/evanchiu/serverless-galleria/issues" }, "homepage": "https://github.com/evanchiu/serverless-galleria#readme", "dependencies": { "jimp": "^0.22.10", "serverless-galleria-util": "1.2.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "eslint": "^8.52.0", "rollup": "^4.1.4" } } ================================================ FILE: compress/rollup.config.js ================================================ import rollupConfig from "serverless-galleria-util/rollup.config.js"; export default rollupConfig; ================================================ FILE: compress/src/index.js ================================================ import Jimp from "jimp"; import { handle } from "serverless-galleria-util"; /** Handle the event from s3 */ export async function handler(event) { const quality = parseInt(process.env.QUALITY); if (typeof quality !== "number") { throw new Error("Error: Environment variable QUALITY missing"); } console.log(`Transforming with quality (${quality})`); await handle(event, async (inBuffer) => { const image = await Jimp.read(inBuffer); image.quality(quality); return image.getBufferAsync(Jimp.MIME_JPEG); }); } ================================================ FILE: compress/template.yml ================================================ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Transforms images by compression to a configured quality level Resources: transform: Type: AWS::Serverless::Function Properties: Description: Transforms images by compression to a configured quality level Handler: bundle.handler Runtime: nodejs18.x CodeUri: bundle.js MemorySize: 1536 Policies: - S3ReadPolicy: BucketName: Ref: sourceBucket - S3WritePolicy: BucketName: Ref: destBucket Timeout: 300 Events: upload: Type: S3 Properties: Bucket: Ref: source Events: s3:ObjectCreated:* Environment: Variables: DEST_BUCKET: Ref: destBucket QUALITY: Ref: quality NODE_OPTIONS: --enable-source-maps source: Type: AWS::S3::Bucket Properties: BucketName: Ref: sourceBucket Parameters: sourceBucket: Type: String Description: Name of the S3 Bucket to read source images from (must NOT exist prior to deployment) destBucket: Type: String Description: Name of the S3 Bucket to put transformed images into (must exist prior to deployment) quality: Type: Number Description: Quality of Jpeg output, 1 to 100, 100 is best quality/largest file Default: 25 ================================================ FILE: crop/README.md ================================================ # crop Copy and crop images using Lambda. ## Deploy with CloudFormation Prerequisites: [Node.js](https://nodejs.org/en/) and [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) installed * Create an [AWS](https://aws.amazon.com/) Account and [IAM User](https://aws.amazon.com/iam/) with the `AdministratorAccess` AWS [Managed Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) * Run `aws configure` to put store that user's credentials in `~/.aws/credentials` * Create an S3 bucket for storing the Lambda code and store its name in a shell variable with: * `export CODE_BUCKET=bucket` * Create the S3 bucket for the cropped output, store its name in shell variable: * `export DEST_BUCKET=bucket` * Choose a name, but do NOT create the S3 bucket input comes from, store its name in shell variable: * `export SOURCE_BUCKET=bucket` * Choose the width in pixels, store it in shell variable: * `export WIDTH=600` * Choose the height in pixels, store it in shell variable: * `export HEIGHT=400` * Choose the x in pixels (number of pixels right from left edge to remove), store it in shell variable: * `export X=50` * Choose the y in pixels (number of pixels down from the top to remove), store it in shell variable: * `export Y=50` * Npm install: * `npm install` * Build: * `npm run build` * Upload package to S3, transform the CloudFormation template: * `npm run package` * Deploy to CloudFormation: * `npm run deploy` ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~crop) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [crop](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~crop) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: crop/SAR_README.md ================================================ # crop Serverless image crop This application is designed to be used as a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) transform, but it can also function as a generic JPEG crop tool. ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Note that if you're using a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) image processing pipeline, this bucket will be created by the following transform, unless this is the last transform. * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~crop) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [crop](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~crop) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: crop/package.json ================================================ { "name": "crop", "version": "1.2.1", "description": "Copy and crop images using Lambda", "type": "module", "main": "src/index.js", "scripts": { "lint": "eslint src", "build": "rollup --config", "package": "aws cloudformation package --template-file template.yml --output-template-file packaged-template.yml --s3-bucket $CODE_BUCKET", "deploy": "aws cloudformation deploy --template-file packaged-template.yml --capabilities CAPABILITY_IAM --stack-name dev-crop-$USER --parameter-overrides sourceBucket=$SOURCE_BUCKET destBucket=$DEST_BUCKET width=$WIDTH height=$HEIGHT xCoordinate=$X yCoordinate=$Y" }, "repository": { "type": "git", "url": "git+https://github.com/evanchiu/serverless-galleria.git" }, "keywords": [ "Crop", "Image", "ImageMagick", "Serverless" ], "author": "Evan Chiu ", "license": "MIT", "bugs": { "url": "https://github.com/evanchiu/serverless-galleria/issues" }, "homepage": "https://github.com/evanchiu/serverless-galleria#readme", "dependencies": { "jimp": "^0.22.10", "serverless-galleria-util": "1.2.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "eslint": "^8.52.0", "rollup": "^4.1.4" } } ================================================ FILE: crop/rollup.config.js ================================================ import rollupConfig from "serverless-galleria-util/rollup.config.js"; export default rollupConfig; ================================================ FILE: crop/src/index.js ================================================ import Jimp from "jimp"; import { handle } from "serverless-galleria-util"; /** Handle the event from s3 */ export async function handler(event) { const width = parseInt(process.env.WIDTH); const height = parseInt(process.env.HEIGHT); let x = parseInt(process.env.X_COORDINATE); let y = parseInt(process.env.Y_COORDINATE); if (typeof width !== "number") { throw new Error("Error: Environment variable WIDTH missing"); } if (typeof height !== "number") { throw new Error("Error: Environment variable HEIGHT missing"); } if (typeof x !== "number") { throw new Error("Error: Environment variable X_COORDINATE missing"); } if (typeof y !== "number") { throw new Error("Error: Environment variable Y_COORDINATE missing"); } console.log(`Cropping ${JSON.stringify({ width, height, x, y })}`); await handle(event, async (inBuffer) => { const image = await Jimp.read(inBuffer); image.crop(x, y, width, height); return image.getBufferAsync(Jimp.MIME_JPEG); }); } ================================================ FILE: crop/template.yml ================================================ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Transforms images by cropping Resources: transform: Type: AWS::Serverless::Function Properties: Description: Transforms images by cropping Handler: bundle.handler Runtime: nodejs18.x CodeUri: bundle.js MemorySize: 1536 Policies: - S3ReadPolicy: BucketName: Ref: sourceBucket - S3WritePolicy: BucketName: Ref: destBucket Timeout: 300 Events: upload: Type: S3 Properties: Bucket: Ref: source Events: s3:ObjectCreated:* Environment: Variables: DEST_BUCKET: Ref: destBucket WIDTH: Ref: width HEIGHT: Ref: height X_COORDINATE: Ref: xCoordinate Y_COORDINATE: Ref: yCoordinate source: Type: AWS::S3::Bucket Properties: BucketName: Ref: sourceBucket Parameters: sourceBucket: Type: String Description: Name of the S3 Bucket to read source images from (must NOT exist prior to deployment) destBucket: Type: String Description: Name of the S3 Bucket to put transformed images into (must exist prior to deployment) width: Type: Number Description: Width of cropped image Default: 600 height: Type: Number Description: Height of cropped image Default: 400 xCoordinate: Type: Number Description: (x, y) is the upper left corner of the cropped region Default: 50 yCoordinate: Type: Number Description: (x, y) is the upper left corner of the cropped region Default: 50 ================================================ FILE: galleria/README.md ================================================ # galleria Serverless photo gallery Demo: https://galleria.evanchiu.com ## Deploy with CloudFormation Prerequisites: [Node.js](https://nodejs.org/en/) and [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) installed * Create an [AWS](https://aws.amazon.com/) Account and [IAM User](https://aws.amazon.com/iam/) with the `AdministratorAccess` AWS [Managed Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) * Run `aws configure` to put store that user's credentials in `~/.aws/credentials` * Create an S3 bucket for storing the Lambda code and store its name in a shell variable with: * `export CODE_BUCKET=bucket` * Create an S3 bucket from which to read the thumbnails, store its name in shell variable: * `export THUMB_BUCKET=bucket` * Create an S3 bucket from which to read the full size images, store its name in shell variable: * `export FULL_BUCKET=bucket` * Npm install: * `npm install` * Build: * `npm run build` * Upload package to S3, transform the CloudFormation template: * `npm run package` * Deploy to CloudFormation: * `npm run deploy` ## Deploy from the AWS Serverless Application Repository * Create the code, thumnail, and full size buckets * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~galleria) page ## Use * Go to [API Gateway](https://console.aws.amazon.com/apigateway/home) in the AWS Console to find the invoke URL and open it in your browser. * Optionally, you can set up a [custom domain](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html) ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [galleria](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~galleria) on the AWS Serverless Application Repository * Theme is [photo](https://freehtml5.co/photo-free-website-template-using-bootstrap-for-photographer/) from [freehtml5.co](https://freehtml5.co) ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: galleria/SAR_README.md ================================================ # galleria Serverless web application displaying a photo gallery This application is designed to be used with [serverless-galleria](https://github.com/evanchiu/serverless-galleria), but it can also function as a generic web interface to images in S3 buckets. ## Deploy * Create an S3 bucket to hold the thumbnail images * Create an S3 bucket to hold the fullsize images * Note that if you're using a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) image processing pipeline, then the fullsize bucket will probably be created by one of your transforms * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~galleria) page ## Use 1. In the [API Gateway Console](https://console.aws.amazon.com/apigateway) 1. Navigate to APIs / aws-serverless-repository-galleria / Settings 1. Hit Add Binary Media Type 1. Enter `*/*` in the box 1. Hit Save Changes 1. Navigate to APIs / aws-serverless-repository-galleria / Resources 1. Click the Actions dropdown 1. Click Deploy API 1. Deployment stage: **prod** 1. Deployment description: *Adding binary support* 1. Hit Deploy 1. Set up a [custom domain name](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html) 1. Open the custom domain in your browser to view the gallery ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [galleria](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~galleria) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: galleria/package.json ================================================ { "name": "galleria", "version": "1.2.1", "description": "Serverless photo gallery", "type": "module", "main": "src/index.js", "scripts": { "lint": "eslint src", "build": "rollup --config && zip -r galleria.zip bundle.js public", "package": "aws cloudformation package --template-file template.yml --output-template-file packaged-template.yml --s3-bucket $CODE_BUCKET", "deploy": "aws cloudformation deploy --template-file packaged-template.yml --capabilities CAPABILITY_IAM --stack-name dev-galleria-$USER --parameter-overrides thumbBucket=$THUMB_BUCKET fullBucket=$FULL_BUCKET" }, "repository": { "type": "git", "url": "git+https://github.com/evanchiu/serverless-galleria.git" }, "keywords": [ "Galleria", "Gallery", "Image", "Photo", "S3", "Serverless" ], "author": "Evan Chiu ", "license": "MIT", "bugs": { "url": "https://github.com/evanchiu/serverless-galleria/issues" }, "homepage": "https://github.com/evanchiu/serverless-galleria#readme", "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "eslint": "^8.52.0", "rollup": "^4.1.4" }, "dependencies": { "mime-types": "^2.1.30", "serverless-galleria-util": "1.2.0" } } ================================================ FILE: galleria/public/index.template.html ================================================ Serverless Galleria
{{photos}}

Hello

Welcome to the Serverless Galleria demo. This a serverless batch photo manipulation and publishing suite of functions. Serverless functions run on demand, so you pay only for the resources you use.

Try it out

Serverless Galleria provides a web interface for drag and drop uploading files, several photo transformations to optimize the images for the web, and this beautiful front end for displaying the photos. To deploy your own stack, get started with the readme.

================================================ FILE: galleria/rollup.config.js ================================================ import rollupConfig from "serverless-galleria-util/rollup.config.js"; export default rollupConfig; ================================================ FILE: galleria/src/index.js ================================================ import { readFile } from "fs/promises"; import { lookup } from "mime-types"; import { join, resolve } from "path"; import { done, get, list } from "../../serverless-galleria-util"; const THUMB_BUCKET = process.env.THUMB_BUCKET; const FULL_BUCKET = process.env.FULL_BUCKET; export async function handler(event) { // Fail on mising config if (!THUMB_BUCKET) { console.error("Error: Environment variable THUMB_BUCKET missing"); return done(500, '{"message":"Internal Server Error"}'); } if (!FULL_BUCKET) { console.error("Error: Environment variable FULL_BUCKET missing"); return done(500, '{"message":"Internal Server Error"}'); } if ( event.path.startsWith("/api/thumb/") || event.path.startsWith("/api/full/") ) { return imageRoute(event); } else { return servePublic(event); } } async function imageRoute(event) { if (event.httpMethod !== "GET") { return done(400, '{"message":"Invalid HTTP Method"}'); } const bucket = event.path.startsWith("/api/thumb/") ? THUMB_BUCKET : FULL_BUCKET; const key = event.path.replace(/\/api\/(full|thumb)\//, ""); const mimeType = lookup(key); try { const data = await get(bucket, key); if ( mimeType === "image/png" || mimeType === "image/jpeg" || mimeType === "image/x-icon" ) { // Base 64 encode binary images console.log(`Serving binary ${bucket}:${key} (${mimeType})`); return done(200, data.toString("base64"), mimeType, true); } else { console.log(`Serving text ${bucket}:${key} (${mimeType})`); return done(200, data.toString(), mimeType); } } catch (error) { console.error(error); return done(500, '{"message":"Internal Server Error"}'); } } async function servePublic(event) { console.log(`Serving public for ${event.path}`); // Set urlPath let urlPath; if (event.path === "/") { return serveIndex(event); } else { urlPath = event.path; } // Determine the file's path on lambda's filesystem const publicPath = join(process.env.LAMBDA_TASK_ROOT, "public"); const filePath = resolve(join(publicPath, urlPath)); const mimeType = lookup(filePath); // Make sure the user doesn't try to break out of the public directory if (!filePath.startsWith(publicPath)) { console.log("forbidden", filePath, publicPath); return done(403, '{"message":"Forbidden"}'); } // Attempt to read the file, give a 404 on error try { const data = await readFile(filePath); if ( mimeType === "image/png" || mimeType === "image/jpeg" || mimeType === "image/x-icon" || mimeType === "application/font-woff" || mimeType === "application/font-woff2" || mimeType === "application/vnd.ms-fontobject" || mimeType === "application/x-font-ttf" ) { // Base 64 encode binary images return done(200, data.toString("base64"), mimeType, true); } else { return done(200, data.toString(), mimeType); } } catch (e) { console.error("404", e); return done(404, '{"message":"Not Found"}'); } } // Serve the index page async function serveIndex(event) { console.log("Serving index"); // Determine base path on whether the API Gateway stage is in the path or not let base_path = "/"; if (event.requestContext.path.startsWith("/" + event.requestContext.stage)) { base_path = "/" + event.requestContext.stage + "/"; } let filePath = join( process.env.LAMBDA_TASK_ROOT, "public/index.template.html" ); const thumbBaseUrl = "https://" + event.headers.Host + base_path + "api/thumb/"; const fullBaseUrl = "https://" + event.headers.Host + base_path + "api/full/"; // Read the file, fill in base_path and serve, or 404 on error try { const [indexTemplate, images] = await Promise.all([ readFile(filePath), list(THUMB_BUCKET), ]); const html = images .map((image) => { return `
\n \n
\n`; }) .join("\n"); let output = indexTemplate.toString().replace("{{photos}}", html); return done(200, output, "text/html"); } catch (error) { console.error("404", error); return done(404, '{"message":"Not Found"}'); } } ================================================ FILE: galleria/template.yml ================================================ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Serverless photo gallery Globals: Api: BinaryMediaTypes: - '*~1*' Resources: galleria: Type: AWS::Serverless::Function Properties: Description: Serverless photo gallery Handler: bundle.handler Runtime: nodejs18.x CodeUri: galleria.zip MemorySize: 1536 Policies: - S3ReadPolicy: BucketName: Ref: thumbBucket - S3ReadPolicy: BucketName: Ref: fullBucket Timeout: 60 Events: root: Type: Api Properties: Path: / Method: get getProxy: Type: Api Properties: Path: '/{proxy+}' Method: get Environment: Variables: THUMB_BUCKET: Ref: thumbBucket FULL_BUCKET: Ref: fullBucket Parameters: thumbBucket: Type: String Description: Name of the S3 Bucket from which to read the thumbnails (must exist prior to deployment) fullBucket: Type: String Description: Name of the S3 Bucket from which to read the full size images (must exist prior to deployment) ================================================ FILE: package.json ================================================ { "name": "serverless-galleria", "version": "1.2.0", "description": "Serverless batch photo upload, manipulation, and publishing", "private": true, "workspaces": [ "blur", "compress", "crop", "galleria", "resize", "rotate", "sepia", "serverless-galleria-util", "uploader" ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Evan Chiu ", "license": "MIT" } ================================================ FILE: resize/README.md ================================================ # resize Copy and resize images using Lambda. ## Max Dimension * For this function, you'll specify the max dimension in pixels. The function will keep the images in their original width/height ratios, limiting the larger dimension to the given maximum. * E.g. with a max dimension of 100, an 800 x 600 image will be 100 x 75, but a 200 x 400 image will be 50 x 100 ## Deploy with CloudFormation Prerequisites: [Node.js](https://nodejs.org/en/) and [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) installed * Create an [AWS](https://aws.amazon.com/) Account and [IAM User](https://aws.amazon.com/iam/) with the `AdministratorAccess` AWS [Managed Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) * Run `aws configure` to put store that user's credentials in `~/.aws/credentials` * Create an S3 bucket for storing the Lambda code and store its name in a shell variable with: * `export CODE_BUCKET=bucket` * Create the S3 bucket for the resized output, store its name in shell variable: * `export DEST_BUCKET=bucket` * Choose a name, but do NOT create the S3 bucket input comes from, store its name in shell variable: * `export SOURCE_BUCKET=bucket` * Choose the max dimension in pixels, store it in shell variable: * `export MAX_DIMENSION=300` * Npm install: * `npm install` * Build: * `npm run build` * Upload package to S3, transform the CloudFormation template: * `npm run package` * Deploy to CloudFormation: * `npm run deploy` ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~resize) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [resize](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~resize) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: resize/SAR_README.md ================================================ # resize Serverless image resize This application is designed to be used as a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) transform, but it can also function as a generic JPEG resizer. ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Note that if you're using a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) image processing pipeline, this bucket will be created by the following transform, unless this is the last transform. * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~resize) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [resize](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~resize) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: resize/package.json ================================================ { "name": "resize", "version": "1.2.1", "description": "Copy and resize images using Lambda", "type": "module", "main": "src/index.js", "scripts": { "lint": "eslint src", "build": "rollup --config", "package": "aws cloudformation package --template-file template.yml --output-template-file packaged-template.yml --s3-bucket $CODE_BUCKET", "deploy": "aws cloudformation deploy --template-file packaged-template.yml --capabilities CAPABILITY_IAM --stack-name dev-resize-$USER --parameter-overrides sourceBucket=$SOURCE_BUCKET destBucket=$DEST_BUCKET maxDimension=$MAX_DIMENSION" }, "repository": { "type": "git", "url": "git+https://github.com/evanchiu/serverless-galleria.git" }, "keywords": [ "Image", "ImageMagick", "Resize", "Serverless" ], "author": "Evan Chiu ", "license": "MIT", "bugs": { "url": "https://github.com/evanchiu/serverless-galleria/issues" }, "homepage": "https://github.com/evanchiu/serverless-galleria#readme", "dependencies": { "jimp": "^0.22.10", "serverless-galleria-util": "1.2.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "eslint": "^8.52.0", "rollup": "^4.1.4" } } ================================================ FILE: resize/rollup.config.js ================================================ import rollupConfig from "serverless-galleria-util/rollup.config.js"; export default rollupConfig; ================================================ FILE: resize/src/index.js ================================================ import Jimp from "jimp"; import { handle } from "serverless-galleria-util"; /** Handle the event from s3 */ export async function handler(event) { const maxDimension = parseInt(process.env.MAX_DIMENSION); if (typeof maxDimension !== "number") { throw new Error("Error: Environment variable MAX_DIMENSION missing"); } console.log(`Resizing to max dimension (${maxDimension})`); await handle(event, async (inBuffer) => { const image = await Jimp.read(inBuffer); image.scaleToFit(maxDimension, maxDimension); return image.getBufferAsync(Jimp.MIME_JPEG); }); } ================================================ FILE: resize/template.yml ================================================ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Transforms images by resizing to a configured max dimension Resources: transform: Type: AWS::Serverless::Function Properties: Description: Transforms images by resizing to a configured max dimension Handler: bundle.handler Runtime: nodejs18.x CodeUri: bundle.js MemorySize: 1536 Policies: - S3ReadPolicy: BucketName: Ref: sourceBucket - S3WritePolicy: BucketName: Ref: destBucket Timeout: 300 Events: upload: Type: S3 Properties: Bucket: Ref: source Events: s3:ObjectCreated:* Environment: Variables: DEST_BUCKET: Ref: destBucket MAX_DIMENSION: Ref: maxDimension source: Type: AWS::S3::Bucket Properties: BucketName: Ref: sourceBucket Parameters: sourceBucket: Type: String Description: Name of the S3 Bucket to read source images from (must NOT exist prior to deployment) destBucket: Type: String Description: Name of the S3 Bucket to put transformed images into (must exist prior to deployment) maxDimension: Type: Number Description: Maximum dimension length in pixels Default: 300 ================================================ FILE: rotate/README.md ================================================ # rotate Copy and rotate images using Lambda. ## Deploy with CloudFormation Prerequisites: [Node.js](https://nodejs.org/en/) and [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) installed * Create an [AWS](https://aws.amazon.com/) Account and [IAM User](https://aws.amazon.com/iam/) with the `AdministratorAccess` AWS [Managed Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) * Run `aws configure` to put store that user's credentials in `~/.aws/credentials` * Create an S3 bucket for storing the Lambda code and store its name in a shell variable with: * `export CODE_BUCKET=bucket` * Create the S3 bucket for the rotated output, store its name in shell variable: * `export DEST_BUCKET=bucket` * Choose a name, but do NOT create the S3 bucket input comes from, store its name in shell variable: * `export SOURCE_BUCKET=bucket` * Choose the number of degrees to rotate, store it in shell variable: * `export ROTATE_DEGREES=30` * Choose the background color in hex (e.g. `#RRGGBB`), store it in shell variable: * `export BACKGROUND_COLOR='#00CCFF'` * Npm install: * `npm install` * Build: * `npm run build` * Upload package to S3, transform the CloudFormation template: * `npm run package` * Deploy to CloudFormation: * `npm run deploy` ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~rotate) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [rotate](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~rotate) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: rotate/SAR_README.md ================================================ # rotate Serverless image rotation This application is designed to be used as a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) transform, but it can also function as a generic JPEG rotator. ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Note that if you're using a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) image processing pipeline, this bucket will be created by the following transform, unless this is the last transform. * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~rotate) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [rotate](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~rotate) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: rotate/package.json ================================================ { "name": "rotate", "version": "1.2.1", "description": "rotate transformation for Serverless Galleria", "type": "module", "main": "src/index.js", "scripts": { "lint": "eslint src", "build": "rollup --config", "package": "aws cloudformation package --template-file template.yml --output-template-file packaged-template.yml --s3-bucket $CODE_BUCKET", "deploy": "aws cloudformation deploy --template-file packaged-template.yml --capabilities CAPABILITY_IAM --stack-name dev-rotate-$USER --parameter-overrides sourceBucket=$SOURCE_BUCKET destBucket=$DEST_BUCKET rotateDegrees=$ROTATE_DEGREES backgroundColor=\"$BACKGROUND_COLOR\"" }, "repository": { "type": "git", "url": "git+https://github.com/evanchiu/serverless-galleria.git" }, "keywords": [ "Image", "ImageMagick", "Rotate", "Serverless" ], "author": "Evan Chiu ", "license": "MIT", "bugs": { "url": "https://github.com/evanchiu/serverless-galleria/issues" }, "homepage": "https://github.com/evanchiu/serverless-galleria#readme", "dependencies": { "jimp": "^0.22.10", "serverless-galleria-util": "1.2.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "eslint": "^8.52.0", "rollup": "^4.1.4" } } ================================================ FILE: rotate/rollup.config.js ================================================ import rollupConfig from "serverless-galleria-util/rollup.config.js"; export default rollupConfig; ================================================ FILE: rotate/src/index.js ================================================ import Jimp from "jimp"; import { handle } from "serverless-galleria-util"; /** Handle the event from s3 */ export async function handler(event) { const rotateDegrees = parseInt(process.env.ROTATE_DEGREES); const backgroundColor = process.env.BACKGROUND_COLOR; if (typeof rotateDegrees !== "number") { throw new Error("Error: Environment variable ROTATE_DEGREES missing"); } if (!backgroundColor.match(/^#[0-9a-fA-F]{6}$/)) { throw new Error("Error: Expected BACKGROUND_COLOR hex format, e.g. \"#00CCFF\"") } console.log(`Rotating ${JSON.stringify(rotateDegrees, backgroundColor)}`); await handle(event, async (inBuffer) => { const image = await Jimp.read(inBuffer); image.background(Jimp.cssColorToHex(backgroundColor)).rotate(rotateDegrees); return image.getBufferAsync(Jimp.MIME_JPEG); }); } ================================================ FILE: rotate/template.yml ================================================ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Transforms images by rotating a configured degree Resources: transform: Type: AWS::Serverless::Function Properties: Description: Transforms images by rotating a configured degree Handler: bundle.handler Runtime: nodejs18.x CodeUri: bundle.js MemorySize: 1536 Policies: - S3ReadPolicy: BucketName: Ref: sourceBucket - S3WritePolicy: BucketName: Ref: destBucket Timeout: 300 Events: upload: Type: S3 Properties: Bucket: Ref: source Events: s3:ObjectCreated:* Environment: Variables: DEST_BUCKET: Ref: destBucket ROTATE_DEGREES: Ref: rotateDegrees BACKGROUND_COLOR: Ref: backgroundColor source: Type: AWS::S3::Bucket Properties: BucketName: Ref: sourceBucket Parameters: sourceBucket: Type: String Description: Name of the S3 Bucket to read source images from (must NOT exist prior to deployment) destBucket: Type: String Description: Name of the S3 Bucket to put transformed images into (must exist prior to deployment) rotateDegrees: Type: Number Description: Number of degrees to rotate clockwise Default: 10 backgroundColor: Type: String Description: Background color to fill in (#RRGGBB) Default: '#000000' ================================================ FILE: sepia/README.md ================================================ # sepia Copy and apply a sepia tone to images using Lambda. ## Deploy with CloudFormation Prerequisites: [Node.js](https://nodejs.org/en/) and [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) installed * Create an [AWS](https://aws.amazon.com/) Account and [IAM User](https://aws.amazon.com/iam/) with the `AdministratorAccess` AWS [Managed Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) * Run `aws configure` to put store that user's credentials in `~/.aws/credentials` * Create an S3 bucket for storing the Lambda code and store its name in a shell variable with: * `export CODE_BUCKET=bucket` * Create the S3 bucket for the sepia-toned output, store its name in shell variable: * `export DEST_BUCKET=bucket` * Choose a name, but do NOT create the S3 bucket input comes from, store its name in shell variable: * `export SOURCE_BUCKET=bucket` * Npm install: * `npm install` * Build: * `npm run build` * Upload package to S3, transform the CloudFormation template: * `npm run package` * Deploy to CloudFormation: * `npm run deploy` ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~sepia) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [sepia](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~sepia) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: sepia/SAR_README.md ================================================ # sepia Serverless image sepia tone application This application is designed to be used as a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) transform, but it can also function as a generic JPEG sepia application. ## Deploy from the AWS Serverless Application Repository * Create the destination bucket * Note that if you're using a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) image processing pipeline, this bucket will be created by the following transform, unless this is the last transform. * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~sepia) page ## Use * Images that you put into the source bucket will be transformed, then put into the destination bucket ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [sepia](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~sepia) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: sepia/package.json ================================================ { "name": "sepia", "version": "1.2.1", "description": "Sepia transformation for Serverless Galleria", "type": "module", "main": "src/index.js", "scripts": { "lint": "eslint src", "build": "rollup --config", "package": "aws cloudformation package --template-file template.yml --output-template-file packaged-template.yml --s3-bucket $CODE_BUCKET", "deploy": "aws cloudformation deploy --template-file packaged-template.yml --capabilities CAPABILITY_IAM --stack-name dev-sepia-$USER --parameter-overrides sourceBucket=$SOURCE_BUCKET destBucket=$DEST_BUCKET" }, "repository": { "type": "git", "url": "git+https://github.com/evanchiu/serverless-galleria.git" }, "keywords": [ "Serverless", "ImageMagick", "Sepia" ], "author": "Evan Chiu ", "license": "MIT", "bugs": { "url": "https://github.com/evanchiu/serverless-galleria/issues" }, "homepage": "https://github.com/evanchiu/serverless-galleria#readme", "dependencies": { "jimp": "^0.22.10", "serverless-galleria-util": "1.2.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "eslint": "^8.52.0", "rollup": "^4.1.4" } } ================================================ FILE: sepia/rollup.config.js ================================================ import rollupConfig from "serverless-galleria-util/rollup.config.js"; export default rollupConfig; ================================================ FILE: sepia/src/index.js ================================================ import Jimp from "jimp"; import { handle } from "serverless-galleria-util"; /** Handle the event from s3 */ export async function handler(event) { console.log(`Applying sepia`); await handle(event, async (inBuffer) => { const image = await Jimp.read(inBuffer); image.sepia(); return image.getBufferAsync(Jimp.MIME_JPEG); }); } ================================================ FILE: sepia/template.yml ================================================ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Transforms images by applying sepia tone Resources: transform: Type: AWS::Serverless::Function Properties: Description: Transforms images by applying sepia tone Handler: bundle.handler Runtime: nodejs18.x CodeUri: bundle.js MemorySize: 1536 Policies: - S3ReadPolicy: BucketName: Ref: sourceBucket - S3WritePolicy: BucketName: Ref: destBucket Timeout: 300 Events: upload: Type: S3 Properties: Bucket: Ref: source Events: s3:ObjectCreated:* Environment: Variables: DEST_BUCKET: Ref: destBucket source: Type: AWS::S3::Bucket Properties: BucketName: Ref: sourceBucket Parameters: sourceBucket: Type: String Description: Name of the S3 Bucket to read source images from (must NOT exist prior to deployment) destBucket: Type: String Description: Name of the S3 Bucket to put transformed images into (must exist prior to deployment) ================================================ FILE: serverless-galleria-util/index.js ================================================ import { GetObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3"; const s3 = new S3Client(); /** Handle the s3 event by loading the s3 record files from s3, running the given transformer on each, saving to the dest bucket */ export async function handle(event, transformer) { // Fail on mising config const destBucket = process.env.DEST_BUCKET; if (!destBucket) { throw new Error("Error: Environment variable DEST_BUCKET missing"); } // Transform all records await Promise.all( event.Records.map(async (record) => { const srcBucket = record.s3.bucket.name; const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, " ")); console.log( `Transforming ${srcBucket}:${key} to ${destBucket}:${key}...` ); const original = await get(srcBucket, key); const modified = await transformer(original); await put(destBucket, key, modified); console.log(`Transformed ${srcBucket}:${key} to ${destBucket}:${key}`); }) ); } /** Get file from S3 as a Buffer */ export async function get(bucket, key) { const response = await s3.send( new GetObjectCommand({ Bucket: bucket, Key: key, }) ); return Buffer.concat(await response.Body.toArray()); } /** Put data into S3 */ export async function put(bucket, key, data) { return s3.send( new PutObjectCommand({ Bucket: bucket, Key: key, Body: data, }) ); } /** List the contents of an S3 bucket (up to first 1000 items) */ export async function list(bucket) { const response = await s3.send(new ListObjectsV2Command({ Bucket: bucket })); return response.Contents; } /** We're done with this API Gateway lambda, return to the client with given parameters */ export function done( statusCode, body, contentType = "application/json", isBase64Encoded = false ) { return { statusCode: statusCode, isBase64Encoded: isBase64Encoded, body: body, headers: { "Content-Type": contentType, }, }; } ================================================ FILE: serverless-galleria-util/package.json ================================================ { "name": "serverless-galleria-util", "version": "1.2.0", "description": "Utility functions shared across serverless galleria lambdas", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "Evan Chiu ", "license": "MIT" } ================================================ FILE: serverless-galleria-util/rollup.config.js ================================================ import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; export default { input: 'src/index.js', output: { file: 'bundle.js', sourcemap: "inline", format: 'cjs' }, external: [/@aws-sdk\/.*/], plugins: [nodeResolve({preferBuiltins: true}), commonjs(), json()] }; ================================================ FILE: uploader/.eslintrc.cjs ================================================ module.exports = { "env": { "es2021": true, "node": true }, "extends": "eslint:recommended", "overrides": [ { "env": { "node": true }, "files": [ ".eslintrc.{js,cjs}" ], "parserOptions": { "sourceType": "script" } } ], "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "rules": { } } ================================================ FILE: uploader/README.md ================================================ # uploader Serverless web application for uploading files to S3 ## Deploy with CloudFormation Prerequisites: [Node.js](https://nodejs.org/en/) and [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) installed * Create an [AWS](https://aws.amazon.com/) Account and [IAM User](https://aws.amazon.com/iam/) with the `AdministratorAccess` AWS [Managed Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) * Run `aws configure` to put store that user's credentials in `~/.aws/credentials` * Create an S3 bucket for storing the Lambda code and store its name in a shell variable with: * `export CODE_BUCKET=bucket` * Create an S3 bucket for saving the uploaded files, store its name in shell variable: * `export DEST_BUCKET=bucket` * Npm install: * `npm install` * Build: * `npm run build` * Upload package to S3, transform the CloudFormation template: * `npm run package` * Deploy to CloudFormation: * `npm run deploy` ## Deploy from the AWS Serverless Application Repository * Create the code and destination buckets * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~uploader) page ## Use * Go to [API Gateway](https://console.aws.amazon.com/apigateway/home) in the AWS Console to find the invoke URL and open it in your browser. * Files you upload will be stored in the configured S3 bucket * Optionally, you can set up a [custom domain](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html) ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [uploader](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~uploader) on the AWS Serverless Application Repository ## Limitations Uploads happen in a single post. The [lambda invocation payload limit is 6 MB](https://docs.aws.amazon.com/lambda/latest/dg/limits.html), and it gets transferred into lambda with [base64](https://en.wikipedia.org/wiki/Base64) encoding, which adds 33% overhead, in addition to the rest of the payload. The expected maximum upload size is around 4 MB. ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: uploader/SAR_README.md ================================================ # uploader Serverless web application for uploading files to S3 This application is designed to be used with [serverless-galleria](https://github.com/evanchiu/serverless-galleria), but it can also function as a generic web to S3 file uploader. ## Deploy * Create an S3 bucket to hold the uploaded content * Note that if you're using a [serverless-galleria](https://github.com/evanchiu/serverless-galleria) image processing pipeline, the bucket you'll upload to will be created by the transform * Hit "Deploy" from the [application](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~uploader) page ## Use 1. In the [API Gateway Console](https://console.aws.amazon.com/apigateway) 1. Navigate to APIs / serverlessrepo-uploader / Dashboard 1. Find the Invocation url, something like *https://xxxxxxxxx.execute-api.region.amazonaws.com/Prod/* 1. (You can also set up [custom domain name](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html)) 1. Open the invocation url in your browser, and drag photos on to the drop point to upload ## Links * [serverless-galleria](https://github.com/evanchiu/serverless-galleria) on Github * [uploader](https://serverlessrepo.aws.amazon.com/#/applications/arn:aws:serverlessrepo:us-east-1:233054207705:applications~uploader) on the AWS Serverless Application Repository ## License © 2017-2023 [Evan Chiu](https://evanchiu.com). This project is available under the terms of the MIT license. ================================================ FILE: uploader/package.json ================================================ { "name": "uploader", "version": "1.2.2", "description": "Serverless web application for uploading files to S3", "type": "module", "main": "src/index.js", "scripts": { "lint": "eslint src", "build": "rollup --config && zip -r uploader.zip bundle.js public", "package": "aws cloudformation package --template-file template.yml --output-template-file packaged-template.yml --s3-bucket $CODE_BUCKET", "deploy": "aws cloudformation deploy --template-file packaged-template.yml --capabilities CAPABILITY_IAM --stack-name dev-uploader-$USER --parameter-overrides destBucket=$DEST_BUCKET" }, "repository": { "type": "git", "url": "git+https://github.com/evanchiu/serverless-galleria.git" }, "keywords": [ "Serverless", "Image", "Upload", "Uploader", "S3" ], "author": "Evan Chiu ", "license": "MIT", "bugs": { "url": "https://github.com/evanchiu/serverless-galleria/issues" }, "homepage": "https://github.com/evanchiu/serverless-galleria#readme", "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "eslint": "^8.52.0", "rollup": "^4.1.4" }, "dependencies": { "mime-types": "^2.1.17", "serverless-galleria-util": "1.2.0" } } ================================================ FILE: uploader/public/index.html ================================================ Uploader

Drop files here!

Uploaded Files:

================================================ FILE: uploader/rollup.config.js ================================================ import rollupConfig from "serverless-galleria-util/rollup.config.js"; export default rollupConfig; ================================================ FILE: uploader/src/index.js ================================================ import { readFile } from "fs/promises"; import { lookup } from "mime-types"; import { join, resolve } from "path"; import { done, put } from "serverless-galleria-util"; const DEST_BUCKET = process.env.DEST_BUCKET; export async function handler(event) { // Fail on mising config if (!DEST_BUCKET) { console.error("Error: Environment variable DEST_BUCKET missing"); return done(500, '{"message":"Internal Server Error"}'); } if (event.path.startsWith("/api/file/")) { return fileRoute(event); } else { return servePublic(event); } } async function fileRoute(event) { console.log("Serving fileRoute"); if (event.httpMethod === "POST") { let key = event.path.replace("/api/file/", ""); // Get the body data let body = event.body; if (event.isBase64Encoded) { console.log("body is base-64 encoded"); body = Buffer.from(event.body, "base64"); } try { await put(DEST_BUCKET, key, body); let message = "Saved " + DEST_BUCKET + ":" + key; console.log(message); return done(200, JSON.stringify({ message })); } catch (error) { console.error(error); return done(500, '{"message":"error saving"}'); } } else { return done(400, '{"message":"Invalid HTTP Method"}'); } } async function servePublic(event) { console.log(`Serving public for ${event.path}`); // Set urlPath let urlPath; if (event.path === "/") { return serveIndex(event); } else { urlPath = event.path; } // Determine the file's path on lambda's filesystem const publicPath = join(process.env.LAMBDA_TASK_ROOT, "public"); const filePath = resolve(join(publicPath, urlPath)); const mimeType = lookup(filePath); // Make sure the user doesn't try to break out of the public directory if (!filePath.startsWith(publicPath)) { console.log("forbidden", filePath, publicPath); return done(403, '{"message":"Forbidden"}', "application/json"); } // Attempt to read the file, give a 404 on error try { const data = await readFile(filePath); if ( mimeType === "image/png" || mimeType === "image/jpeg" || mimeType === "image/x-icon" ) { // Base 64 encode binary images return done(200, data.toString("base64"), mimeType, true); } else { return done(200, data.toString(), mimeType); } } catch (e) { console.error("404", e); return done(404, '{"message":"Not Found"}'); } } // Serve the index page async function serveIndex(event) { console.log("Serving index"); // Determine base path on whether the API Gateway stage is in the path or not let base_path = "/"; if (event.requestContext.path.startsWith("/" + event.requestContext.stage)) { base_path = "/" + event.requestContext.stage + "/"; } let filePath = join(process.env.LAMBDA_TASK_ROOT, "public/index.html"); // Read the file, fill in base_path and serve, or 404 on error try { const data = await readFile(filePath); let content = data.toString().replace(/{{base_path}}/g, base_path); return done(200, content, "text/html"); } catch (error) { console.error("404", error); return done(404, '{"message":"Not Found"}'); } } ================================================ FILE: uploader/template.yml ================================================ AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Serverless web application for uploading files to S3 Globals: Api: BinaryMediaTypes: - '*~1*' Resources: uploader: Type: AWS::Serverless::Function Properties: Description: Serverless web application for uploading files to S3 Handler: src/index.handler Runtime: nodejs18.x CodeUri: package.zip MemorySize: 1536 Policies: - S3WritePolicy: BucketName: Ref: destBucket Timeout: 60 Events: root: Type: Api Properties: Path: / Method: get getProxy: Type: Api Properties: Path: '/{proxy+}' Method: get postProxy: Type: Api Properties: Path: '/{proxy+}' Method: post Environment: Variables: DEST_BUCKET: Ref: destBucket Parameters: destBucket: Type: String Description: Name of the S3 Bucket to put uploaded files into (must exist prior to deployment)