Skip to content

Commit a6f6a03

Browse files
committed
add : Image Upload, Validator Uploading to AWS
0 parents commit a6f6a03

File tree

13 files changed

+5426
-0
lines changed

13 files changed

+5426
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.env
2+
.serverless
3+
.serverless_plugins
4+
node_modules
5+
secrets.dev.yml
6+
*.log

config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import dotenv from 'dotenv';
2+
3+
const parseEnv = async (): Promise<dotenv.DotenvParseOutput> => {
4+
// Load env vars into Serverless environment
5+
// You can do more complicated env var resolution with dotenv here
6+
const envVars = dotenv.config({ path: '.env' }).parsed;
7+
const envObj = Object.assign(
8+
{},
9+
envVars // `dotenv` environment variables
10+
);
11+
return envObj;
12+
};
13+
14+
export default parseEnv;

package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "aws-lambda",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "handler.js",
6+
"scripts": {
7+
"dev": "sls offline -s dev --noTimeout"
8+
},
9+
"author": "",
10+
"license": "ISC",
11+
"dependencies": {
12+
"@aws-sdk/client-s3": "^3.54.0",
13+
"aws-lambda": "^1.0.7",
14+
"jsonwebtoken": "^8.5.1",
15+
"jwks-rsa": "^2.0.5",
16+
"zod": "^3.13.4"
17+
},
18+
"devDependencies": {
19+
"@types/aws-lambda": "^8.10.93",
20+
"@types/jsonwebtoken": "^8.5.8",
21+
"@types/serverless": "^3.0.1",
22+
"serverless": "^3.7.4",
23+
"serverless-iam-roles-per-function": "^3.2.0",
24+
"serverless-offline": "^8.5.0",
25+
"serverless-plugin-typescript": "^2.1.1",
26+
"typescript": "^4.6.2"
27+
}
28+
}

serverless.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
service: upload-to-s3
2+
frameworkVersion: '3'
3+
4+
plugins:
5+
- serverless-offline
6+
- serverless-plugin-typescript
7+
- serverless-iam-roles-per-function
8+
9+
custom:
10+
fileUploadBucketName: ${self:service}-bucket-${self:provider.stage}
11+
serverless-offline:
12+
httpPort: 1337
13+
14+
provider:
15+
name: aws
16+
runtime: nodejs12.x
17+
profile: serverlessUser
18+
stage: dev
19+
region: ap-south-1
20+
21+
functions:
22+
hello:
23+
handler: src/lambda/hello.handler
24+
events:
25+
- http:
26+
path: /hello
27+
method: GET
28+
cors: true
29+
30+
imageUpload:
31+
handler: src/lambda/imageUpload.handler
32+
events:
33+
- http:
34+
path: /imageUpload
35+
method: POST
36+
cors: true
37+
iamRoleStatements:
38+
- Effect: Allow
39+
Action:
40+
- s3:PutObjectAcl
41+
- s3:PutObject
42+
Resource: arn:aws:s3:::${self:custom.fileUploadBucketName}/*
43+
environment:
44+
FILE_UPLOAD_BUCKET_NAME: ${self:custom.fileUploadBucketName}
45+
46+
resources:
47+
Resources:
48+
FileBucket:
49+
Type: AWS::S3::Bucket
50+
Properties:
51+
BucketName: ${self:custom.fileUploadBucketName}
52+
AccessControl: PublicRead

src/lambda/hello.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { APIGatewayProxyHandler, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
2+
import { apiResponse } from '../utils/apiResponse';
3+
4+
export const handler: APIGatewayProxyHandler = async (_event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
5+
try {
6+
return apiResponse(200, {
7+
message: 'Welcome to the API',
8+
});
9+
} catch (err) {
10+
return apiResponse(500, 'Somrthing went wrong');
11+
}
12+
};

src/lambda/imageUpload.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { APIGatewayProxyHandler, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
2+
import { apiResponse } from '../utils/apiResponse';
3+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
4+
import { accessTokenSchemaType, imageMimeSchema, imageUploadSchema } from '../utils/validators';
5+
import { getKey } from '../utils/apiKey';
6+
import { verify } from 'jsonwebtoken';
7+
import { base64MimeType } from '../utils/getMimeType';
8+
9+
const client = new S3Client({ region: process.env['AWS_REGION'] as string });
10+
11+
export const handler: APIGatewayProxyHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
12+
try {
13+
const { body } = event;
14+
if (!body) {
15+
return apiResponse(400, 'Body is required');
16+
}
17+
const values = JSON.parse(body);
18+
const data = imageUploadSchema.safeParse(values);
19+
if (!data.success) {
20+
return apiResponse(400, data.error);
21+
}
22+
const { file, accessToken, fileName, type } = data.data;
23+
24+
if (!file) return apiResponse(400, 'File is required');
25+
26+
//Valid Image Type
27+
const mimeType = base64MimeType(file);
28+
const mimeData = imageMimeSchema.safeParse({ mimeType });
29+
if (!mimeData.success) {
30+
return apiResponse(400, mimeData.error);
31+
}
32+
33+
//Check Valid User Token
34+
const key = await getKey();
35+
const decoded = verify(accessToken, key, { algorithms: ['RS256'] });
36+
const { address } = decoded as accessTokenSchemaType;
37+
38+
//Check File Size
39+
const sanitisedFile = file.replace(/^data:image\/\w+;base64,/, '');
40+
const decodedFile = Buffer.from(sanitisedFile, 'base64');
41+
const sizeInBytes = 4 * Math.ceil(sanitisedFile.length / 3) * 0.5624896334383812;
42+
const sizeInMb = +(sizeInBytes / (1000 * 1024)).toFixed(4);
43+
44+
if (type === 'profile' && sizeInMb > 2) {
45+
return apiResponse(400, 'Profile image size should be less than 2MB');
46+
}
47+
48+
if (type === 'banner' && sizeInMb > 5) {
49+
return apiResponse(400, 'Banner image size should be less than 5MB');
50+
}
51+
52+
//Upload to s3
53+
const date = new Date().toISOString();
54+
const params = {
55+
Bucket: process.env['FILE_UPLOAD_BUCKET_NAME'],
56+
Key: `${address}-${fileName}-${date}.${mimeType?.split('/')[1]}`,
57+
Body: decodedFile,
58+
};
59+
60+
const command = new PutObjectCommand(params);
61+
await client.send(command);
62+
63+
const hrefLink = `https://${command.input.Bucket}.s3.ap-south-1.amazonaws.com/${command.input.Key}`;
64+
return apiResponse(200, {
65+
hrefLink,
66+
message: 'Image Uploaded Successfully',
67+
});
68+
} catch (err) {
69+
console.log(err);
70+
return apiResponse(401, err);
71+
}
72+
};

src/utils/ParseEnv.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import dotenv from 'dotenv';
2+
3+
export const parseEnv = async (): Promise<dotenv.DotenvParseOutput> => {
4+
// Load env vars into Serverless environment
5+
// You can do more complicated env var resolution with dotenv here
6+
const envVars = dotenv.config({ path: '.env' }).parsed;
7+
const envObj = Object.assign(
8+
{},
9+
envVars // `dotenv` environment variables
10+
);
11+
console.log(envObj);
12+
13+
return envObj;
14+
};

src/utils/apiKey.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { JwksClient } from 'jwks-rsa';
2+
3+
const client = new JwksClient({
4+
jwksUri: process.env['JWKS_URI'] as string,
5+
cache: true, // Default Value
6+
cacheMaxEntries: 5, // Default value
7+
cacheMaxAge: 600000, // Defaults to 10m
8+
});
9+
10+
export const getKey = async () => {
11+
const kid = process.env['KEY_KID'];
12+
const key = await client.getSigningKey(kid);
13+
return key.getPublicKey();
14+
};

src/utils/apiResponse.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const apiResponse = (statusCode: number, body: any) => {
2+
return {
3+
headers: {
4+
'Content-Type': 'application/json',
5+
'Access-Control-Allowed-Methods': '*',
6+
'Access-Control-Allow-Origin': '*',
7+
},
8+
statusCode,
9+
body: JSON.stringify(body),
10+
};
11+
};

src/utils/getMimeType.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const base64MimeType = (encoded: string) => {
2+
let result = null;
3+
if (typeof encoded !== 'string') {
4+
return result;
5+
}
6+
const mime = encoded.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/);
7+
8+
if (mime && mime.length) {
9+
result = mime[1];
10+
}
11+
12+
return result;
13+
};

0 commit comments

Comments
 (0)