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](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

* [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](galleria) - Beautiful web interface for displaying photo gallery
## Setup
First, plan your pipeline, as you'll build it backwards. Here's a sample:

### 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 <evan@evanchiu.com>",
"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 <evan@evanchiu.com>",
"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 <evan@evanchiu.com>",
"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 <evan@evanchiu.com>",
"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
================================================
<!DOCTYPE HTML>
<html>
<head>
<title>Serverless Galleria</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="https://galleria-static.evanchiu.com/assets/css/main.css" />
<noscript><link rel="stylesheet" href="https://galleria-static.evanchiu.com/assets/css/noscript.css" /></noscript>
</head>
<body class="is-preload">
<!-- Wrapper -->
<div id="wrapper">
<!-- Header -->
<header id="header">
<h1><strong>Serverless Galleria</strong> by Evan Chiu</h1>
<nav>
<ul>
<li><a href="#footer" class="icon solid fa-info-circle">About</a></li>
</ul>
</nav>
</header>
<!-- Main -->
<div id="main">
{{photos}}
</div>
<!-- Footer -->
<footer id="footer" class="panel">
<div class="inner split">
<div>
<section>
<h2>Hello</h2>
<p>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.</p>
</section>
<section>
<ul class="icons">
<li><a href="https://github.com/evanchiu" class="icon brands fa-github"><span class="label">GitHub</span></a></li>
<li><a href="https://linkedin.com/in/evanchiu" class="icon brands fa-linkedin-in"><span class="label">LinkedIn</span></a></li>
<li><a href="https://twitter.com/evanchiu" class="icon brands fa-twitter"><span class="label">Twitter</span></a></li>
<li><a href="https://dribbble.com/evanchiu" class="icon brands fa-dribbble"><span class="label">Dribbble</span></a></li>
<li><a href="https://instagram.com/evanchiu" class="icon brands fa-instagram"><span class="label">Instagram</span></a></li>
<li><a href="https://facebook.com/evan.chiu" class="icon brands fa-facebook-f"><span class="label">Facebook</span></a></li>
</ul>
</section>
<p class="copyright">
©2017-2023 <a href="https://evanchiu.com/">Evan Chiu</a>. Design: <a href="https://html5up.net/multiverse">Multiverse</a> by <a href="http://html5up.net">HTML5 UP</a>.
</p>
</div>
<div>
<section>
<h2>Try it out</h2>
<p>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 <a href="https://github.com/evanchiu/serverless-galleria">readme</a>.</p>
</section>
</div>
</div>
</footer>
</div>
<!-- Scripts -->
<script src="https://galleria-static.evanchiu.com/assets/js/jquery.min.js"></script>
<script src="https://galleria-static.evanchiu.com/assets/js/jquery.poptrox.min.js"></script>
<script src="https://galleria-static.evanchiu.com/assets/js/browser.min.js"></script>
<script src="https://galleria-static.evanchiu.com/assets/js/breakpoints.min.js"></script>
<script src="https://galleria-static.evanchiu.com/assets/js/util.js"></script>
<script src="https://galleria-static.evanchiu.com/assets/js/main.js"></script>
</body>
</html>
================================================
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 `<article class="thumb">\n <a href="${
fullBaseUrl + image.Key
}" class="image"><img src="${
thumbBaseUrl + image.Key
}" alt="" /></a>\n</article>\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 <evan@evanchiu.com>",
"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 <evan@evanchiu.com>",
"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 <evan@evanchiu.com>",
"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 <evan@evanchiu.com>",
"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 <evan@evanchiu.com>",
"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 <evan@evanchiu.com>",
"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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>Uploader</title>
<style>
.aligner {
text-align: center;
}
#drop {
height: 300px;
line-height: 300px;
width: 300px;
border-radius: 150px;
margin-left: auto;
margin-right: auto;
background-color: #f60;
color: #fff;
text-align: center;
}
</style>
</head>
<body>
<div class="aligner">
<div id="drop"><h2>Drop files here!</h2></div>
<div id="list">
<h2>Uploaded Files:</h2>
</div>
</div>
<script type="text/javascript">
let drop = document.getElementById('drop');
let list = document.getElementById('list');
let basePath = '{{base_path}}'; // filled in by the lambda before serving this page
let uploadBaseUrl = basePath + 'api/file/';
// Do nothing on drag events
function cancel(e) {
e.preventDefault();
return false;
}
// Upload on drop events
function handleDrop(e) {
e.preventDefault();
let dt = e.dataTransfer;
let files = dt.files;
for (let i = 0; i<files.length; i++) {
let file = files[i];
let reader = new FileReader();
reader.addEventListener('loadend', function(e){
fetch(uploadBaseUrl + file.name, {
method: "POST",
body: new Blob([reader.result], {type: file.type})
})
.then((response) => {
if (response.ok) {
let uploadedFileNode = document.createElement('div');
uploadedFileNode.innerHTML = file.name;
list.appendChild(uploadedFileNode);
} else {
alert('Error uploading [' + file.name + ']. Max upload size is ~4MB.');
}
});
});
reader.readAsArrayBuffer(file);
}
return false;
}
// Listen to events
drop.addEventListener('dragenter', cancel);
drop.addEventListener('dragover', cancel);
drop.addEventListener('drop', handleDrop);
</script>
</body>
<!-- Based on https://www.netlify.com/blog/2016/11/17/serverless-file-uploads/ -->
</html>
================================================
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)
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
SYMBOL INDEX (22 symbols across 9 files)
FILE: blur/src/index.js
function handler (line 5) | async function handler(event) {
FILE: compress/src/index.js
function handler (line 5) | async function handler(event) {
FILE: crop/src/index.js
function handler (line 5) | async function handler(event) {
FILE: galleria/src/index.js
constant THUMB_BUCKET (line 6) | const THUMB_BUCKET = process.env.THUMB_BUCKET;
constant FULL_BUCKET (line 7) | const FULL_BUCKET = process.env.FULL_BUCKET;
function handler (line 9) | async function handler(event) {
function imageRoute (line 30) | async function imageRoute(event) {
function servePublic (line 61) | async function servePublic(event) {
function serveIndex (line 106) | async function serveIndex(event) {
FILE: resize/src/index.js
function handler (line 5) | async function handler(event) {
FILE: rotate/src/index.js
function handler (line 5) | async function handler(event) {
FILE: sepia/src/index.js
function handler (line 5) | async function handler(event) {
FILE: serverless-galleria-util/index.js
function handle (line 10) | async function handle(event, transformer) {
function get (line 34) | async function get(bucket, key) {
function put (line 45) | async function put(bucket, key, data) {
function list (line 56) | async function list(bucket) {
function done (line 62) | function done(
FILE: uploader/src/index.js
constant DEST_BUCKET (line 6) | const DEST_BUCKET = process.env.DEST_BUCKET;
function handler (line 8) | async function handler(event) {
function fileRoute (line 22) | async function fileRoute(event) {
function servePublic (line 49) | async function servePublic(event) {
function serveIndex (line 90) | async function serveIndex(event) {
Condensed preview — 60 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
{
"path": ".eslintrc.cjs",
"chars": 508,
"preview": "module.exports = {\n \"env\": {\n \"es2021\": true,\n \"node\": true\n },\n \"extends\": \"eslint:recommended\","
},
{
"path": ".gitignore",
"chars": 976,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directo"
},
{
"path": ".jshintrc",
"chars": 41,
"preview": "{\n \"node\": true,\n \"esversion\": 6\n}\n"
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2017-2023 Evan Chiu\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 2661,
"preview": "# serverless-galleria\n\nServerless batch photo manipulation and publishing\n\n## Design\n### Uploader\nThe Uploader stores im"
},
{
"path": "blur/README.md",
"chars": 1955,
"preview": "# blur\n\nCopy and apply a blur to images using Lambda.\n\n## Deploy with CloudFormation\n\nPrerequisites: [Node.js](https://n"
},
{
"path": "blur/SAR_README.md",
"chars": 1183,
"preview": "# blur\n\nServerless image blur\n\nThis application is designed to be used as a [serverless-galleria](https://github.com/eva"
},
{
"path": "blur/package.json",
"chars": 1303,
"preview": "{\n \"name\": \"blur\",\n \"version\": \"1.2.1\",\n \"description\": \"Blur transformation for Serverless Galleria\",\n \"type\": \"mod"
},
{
"path": "blur/rollup.config.js",
"chars": 98,
"preview": "import rollupConfig from \"serverless-galleria-util/rollup.config.js\";\nexport default rollupConfig;"
},
{
"path": "blur/src/index.js",
"chars": 561,
"preview": "import Jimp from \"jimp\";\nimport { handle } from \"serverless-galleria-util\";\n\n/** Handle the event from s3 */\nexport asyn"
},
{
"path": "blur/template.yml",
"chars": 1414,
"preview": "AWSTemplateFormatVersion: 2010-09-09\nTransform: AWS::Serverless-2016-10-31\nDescription: Transforms images by applying bl"
},
{
"path": "compress/README.md",
"chars": 2095,
"preview": "# compress\n\nCopy and compress images using Lambda.\n\n## Deploy with CloudFormation\n\nPrerequisites: [Node.js](https://node"
},
{
"path": "compress/SAR_README.md",
"chars": 1216,
"preview": "# compress\n\nServerless image compression\n\nThis application is designed to be used as a [serverless-galleria](https://git"
},
{
"path": "compress/package.json",
"chars": 1325,
"preview": "{\n \"name\": \"compress\",\n \"version\": \"1.2.1\",\n \"description\": \"Copy and compress images using Lambda\",\n \"type\": \"modul"
},
{
"path": "compress/rollup.config.js",
"chars": 98,
"preview": "import rollupConfig from \"serverless-galleria-util/rollup.config.js\";\nexport default rollupConfig;"
},
{
"path": "compress/src/index.js",
"chars": 538,
"preview": "import Jimp from \"jimp\";\nimport { handle } from \"serverless-galleria-util\";\n\n/** Handle the event from s3 */\nexport asyn"
},
{
"path": "compress/template.yml",
"chars": 1447,
"preview": "AWSTemplateFormatVersion: 2010-09-09\nTransform: AWS::Serverless-2016-10-31\nDescription: Transforms images by compression"
},
{
"path": "crop/README.md",
"chars": 2261,
"preview": "# crop\n\nCopy and crop images using Lambda.\n\n## Deploy with CloudFormation\n\nPrerequisites: [Node.js](https://nodejs.org/e"
},
{
"path": "crop/SAR_README.md",
"chars": 1186,
"preview": "# crop\n\nServerless image crop\n\nThis application is designed to be used as a [serverless-galleria](https://github.com/eva"
},
{
"path": "crop/package.json",
"chars": 1340,
"preview": "{\n \"name\": \"crop\",\n \"version\": \"1.2.1\",\n \"description\": \"Copy and crop images using Lambda\",\n \"type\": \"module\",\n \"m"
},
{
"path": "crop/rollup.config.js",
"chars": 98,
"preview": "import rollupConfig from \"serverless-galleria-util/rollup.config.js\";\nexport default rollupConfig;"
},
{
"path": "crop/src/index.js",
"chars": 1013,
"preview": "import Jimp from \"jimp\";\nimport { handle } from \"serverless-galleria-util\";\n\n/** Handle the event from s3 */\nexport asyn"
},
{
"path": "crop/template.yml",
"chars": 1758,
"preview": "AWSTemplateFormatVersion: 2010-09-09\nTransform: AWS::Serverless-2016-10-31\nDescription: Transforms images by cropping\nRe"
},
{
"path": "galleria/README.md",
"chars": 2242,
"preview": "# galleria\n\nServerless photo gallery\n\nDemo: https://galleria.evanchiu.com\n\n## Deploy with CloudFormation\n\nPrerequisites:"
},
{
"path": "galleria/SAR_README.md",
"chars": 1814,
"preview": "# galleria\n\nServerless web application displaying a photo gallery\n\nThis application is designed to be used with [serverl"
},
{
"path": "galleria/package.json",
"chars": 1347,
"preview": "{\n \"name\": \"galleria\",\n \"version\": \"1.2.1\",\n \"description\": \"Serverless photo gallery\",\n \"type\": \"module\",\n \"main\":"
},
{
"path": "galleria/public/index.template.html",
"chars": 3524,
"preview": "<!DOCTYPE HTML>\n<html>\n <head>\n <title>Serverless Galleria</title>\n <meta charset=\"utf-8\" />\n <meta name=\"view"
},
{
"path": "galleria/rollup.config.js",
"chars": 98,
"preview": "import rollupConfig from \"serverless-galleria-util/rollup.config.js\";\nexport default rollupConfig;"
},
{
"path": "galleria/src/index.js",
"chars": 4382,
"preview": "import { readFile } from \"fs/promises\";\nimport { lookup } from \"mime-types\";\nimport { join, resolve } from \"path\";\nimpor"
},
{
"path": "galleria/template.yml",
"chars": 1248,
"preview": "AWSTemplateFormatVersion: 2010-09-09\nTransform: AWS::Serverless-2016-10-31\nDescription: Serverless photo gallery\n\nGlobal"
},
{
"path": "package.json",
"chars": 467,
"preview": "{\n \"name\": \"serverless-galleria\",\n \"version\": \"1.2.0\",\n \"description\": \"Serverless batch photo upload, manipulation, "
},
{
"path": "resize/README.md",
"chars": 2283,
"preview": "# resize\n\nCopy and resize images using Lambda.\n\n## Max Dimension\n* For this function, you'll specify the max dimension i"
},
{
"path": "resize/SAR_README.md",
"chars": 1194,
"preview": "# resize\n\nServerless image resize\n\nThis application is designed to be used as a [serverless-galleria](https://github.com"
},
{
"path": "resize/package.json",
"chars": 1318,
"preview": "{\n \"name\": \"resize\",\n \"version\": \"1.2.1\",\n \"description\": \"Copy and resize images using Lambda\",\n \"type\": \"module\",\n"
},
{
"path": "resize/rollup.config.js",
"chars": 98,
"preview": "import rollupConfig from \"serverless-galleria-util/rollup.config.js\";\nexport default rollupConfig;"
},
{
"path": "resize/src/index.js",
"chars": 586,
"preview": "import Jimp from \"jimp\";\nimport { handle } from \"serverless-galleria-util\";\n\n/** Handle the event from s3 */\nexport asyn"
},
{
"path": "resize/template.yml",
"chars": 1381,
"preview": "AWSTemplateFormatVersion: 2010-09-09\nTransform: AWS::Serverless-2016-10-31\nDescription: Transforms images by resizing to"
},
{
"path": "rotate/README.md",
"chars": 2090,
"preview": "# rotate\n\nCopy and rotate images using Lambda.\n\n## Deploy with CloudFormation\n\nPrerequisites: [Node.js](https://nodejs.o"
},
{
"path": "rotate/SAR_README.md",
"chars": 1196,
"preview": "# rotate\n\nServerless image rotation\n\nThis application is designed to be used as a [serverless-galleria](https://github.c"
},
{
"path": "rotate/package.json",
"chars": 1368,
"preview": "{\n \"name\": \"rotate\",\n \"version\": \"1.2.1\",\n \"description\": \"rotate transformation for Serverless Galleria\",\n \"type\": "
},
{
"path": "rotate/rollup.config.js",
"chars": 98,
"preview": "import rollupConfig from \"serverless-galleria-util/rollup.config.js\";\nexport default rollupConfig;"
},
{
"path": "rotate/src/index.js",
"chars": 834,
"preview": "import Jimp from \"jimp\";\nimport { handle } from \"serverless-galleria-util\";\n\n/** Handle the event from s3 */\nexport asyn"
},
{
"path": "rotate/template.yml",
"chars": 1541,
"preview": "AWSTemplateFormatVersion: 2010-09-09\nTransform: AWS::Serverless-2016-10-31\nDescription: Transforms images by rotating a "
},
{
"path": "sepia/README.md",
"chars": 1881,
"preview": "# sepia\n\nCopy and apply a sepia tone to images using Lambda.\n\n## Deploy with CloudFormation\n\nPrerequisites: [Node.js](ht"
},
{
"path": "sepia/SAR_README.md",
"chars": 1216,
"preview": "# sepia\n\nServerless image sepia tone application\n\nThis application is designed to be used as a [serverless-galleria](htt"
},
{
"path": "sepia/package.json",
"chars": 1283,
"preview": "{\n \"name\": \"sepia\",\n \"version\": \"1.2.1\",\n \"description\": \"Sepia transformation for Serverless Galleria\",\n \"type\": \"m"
},
{
"path": "sepia/rollup.config.js",
"chars": 98,
"preview": "import rollupConfig from \"serverless-galleria-util/rollup.config.js\";\nexport default rollupConfig;"
},
{
"path": "sepia/src/index.js",
"chars": 346,
"preview": "import Jimp from \"jimp\";\nimport { handle } from \"serverless-galleria-util\";\n\n/** Handle the event from s3 */\nexport asyn"
},
{
"path": "sepia/template.yml",
"chars": 1186,
"preview": "AWSTemplateFormatVersion: 2010-09-09\nTransform: AWS::Serverless-2016-10-31\nDescription: Transforms images by applying se"
},
{
"path": "serverless-galleria-util/index.js",
"chars": 2054,
"preview": "import {\n GetObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n} from \"@aws-sdk/client-s3\";\nconst "
},
{
"path": "serverless-galleria-util/package.json",
"chars": 324,
"preview": "{\n \"name\": \"serverless-galleria-util\",\n \"version\": \"1.2.0\",\n \"description\": \"Utility functions shared across serverle"
},
{
"path": "serverless-galleria-util/rollup.config.js",
"chars": 375,
"preview": "import { nodeResolve } from '@rollup/plugin-node-resolve';\nimport commonjs from '@rollup/plugin-commonjs';\nimport json f"
},
{
"path": "uploader/.eslintrc.cjs",
"chars": 508,
"preview": "module.exports = {\n \"env\": {\n \"es2021\": true,\n \"node\": true\n },\n \"extends\": \"eslint:recommended\","
},
{
"path": "uploader/README.md",
"chars": 2372,
"preview": "# uploader\n\nServerless web application for uploading files to S3\n\n## Deploy with CloudFormation\n\nPrerequisites: [Node.js"
},
{
"path": "uploader/SAR_README.md",
"chars": 1539,
"preview": "# uploader\n\nServerless web application for uploading files to S3\n\nThis application is designed to be used with [serverle"
},
{
"path": "uploader/package.json",
"chars": 1335,
"preview": "{\n \"name\": \"uploader\",\n \"version\": \"1.2.2\",\n \"description\": \"Serverless web application for uploading files to S3\",\n "
},
{
"path": "uploader/public/index.html",
"chars": 2111,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>Uploader</title>\n <style>\n .aligner {\n text-align: center;\n "
},
{
"path": "uploader/rollup.config.js",
"chars": 98,
"preview": "import rollupConfig from \"serverless-galleria-util/rollup.config.js\";\nexport default rollupConfig;"
},
{
"path": "uploader/src/index.js",
"chars": 3204,
"preview": "import { readFile } from \"fs/promises\";\nimport { lookup } from \"mime-types\";\nimport { join, resolve } from \"path\";\nimpor"
},
{
"path": "uploader/template.yml",
"chars": 1141,
"preview": "AWSTemplateFormatVersion: 2010-09-09\nTransform: AWS::Serverless-2016-10-31\nDescription: Serverless web application for u"
}
]
About this extraction
This page contains the full source code of the evanchiu/serverless-galleria GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 60 files (74.5 KB), approximately 20.9k tokens, and a symbol index with 22 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.