In this project we will upload an image and get image with different sizes.
We will use nanoid
to create ids for images. Sharp
to resize images and zod
for models.
# classes/Image/package.json
"dependencies": {
"@retter/rdk": "1.1.15",
"nanoid": "3.3.4",
"sharp": "0.30.6",
"zod": "3.19.1"
},
"devDependencies": {
"@types/node": "17.0.45",
"@types/sharp": "0.31.0",
"ts-node": "10.9.1"
}
As you can see we will have 3 functions upload
, get
and remove
.
# classes/Image/template.yml
init: index.init
getInstanceId: index.getInstanceId
methods:
- method: get
type: STATIC
handler: image.get
- method: upload
type: STATIC
inputModel: UploadInput
handler: image.upload
- method: remove
type: STATIC
inputModel: RemoveInput
handler: image.remove
Models
Export uploadInput
and removeImageInput
to use as inputModel.
import { z } from 'zod'
const imageId = z.string().regex(/^[\dA-Za-z-]{5,50}$/)
export const uploadInput = z.object({
content: z.string()
})
export const resizedImageParameters = z.object({
id: z.string(),
width: z
.preprocess((value) => Number.parseInt(value as string, 10), z.number())
.optional()
.default(128),
height: z
.preprocess((value) => Number.parseInt(value as string, 10), z.number())
.optional()
.default(128),
quality: z.enum(['low', 'default', 'medium', 'high']).optional().default('default'),
fit: z.enum(['contain', 'cover', 'fill', 'inside', 'outside']).optional().default('inside'),
content: z.any(),
})
export const removeImageInput = z.object({
imageId: z.string()
})
export const parsedPath = z.object({
imageId: z.string(),
width: z.string().optional(),
height: z.string().optional(),
quality: z.string().optional(),
format: z.string().optional(),
fit: z.string().optional(),
})
export type UploadInput = z.infer<typeof uploadInput>
export type ResizedImageParameters = z.infer<typeof resizedImageParameters>
export type RemoveImageInput = z.infer<typeof removeImageInput>
export type ParsedPath = z.infer<typeof parsedPath>
Index.ts
Authorizer just returns statusCode: 200
. And getInstanceId
is like below.
// classes/Image/index.ts
export async function getInstanceId(): Promise<string> {
return "default"
}
Image.ts
const imagePrefix = 'IMAGE_'
const defaultImage = 'defaultImage'
Upload Function
Here content
is base64 encoded image. Checking if content
is in base64 format.
// classes/Image/image.ts
const { content } = data.request.body as UploadInput
const projectId = data.context.projectId
// 10 Character ID
const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-', 10)
const imageId = nanoid();
if (Buffer.from(content, 'base64').toString('base64') !== content) {
throw new Error('Content is not base-64 format')
}
After that uploading image with generated id and checking if upload succeeded.
// classes/Image/image.ts
const setFileResponse = await rdk.setFile({
filename: imagePrefix + imageId,
body: content,
})
if (!setFileResponse?.success) {
throw new Error('Image could not be uploaded!')
}
Lastly our functions response is like below.
// classes/Image/image.ts
data.response = {
statusCode: 200,
body: {
message: 'Image has been uploaded',
id: imageId,
url: `https://${projectId}.api.retter.io/${projectId}/CALL/Image/get/${imageId}_1024x1024_default_inside.png`
},
}
Get Function
This function will be called by an url like this. Example
// classes/Image/image.ts
const path = data.context.pathParameters.path
if (!path) {
throw new Error('Path does not exist')
}
const newPath = parsePath(path)
parsePath function is below. This function splits url path with _
, .
, x
seperators. Example imageId_500x500_high_cover.png
. This way we are giving parameters of function with url.
// classes/Image/image.ts
const parsePath = (path: string): ParsedPath => {
// Get path
const fullPath = path.split('/')[1]
// Get before "." and split it every "_"
const ids = fullPath.split('.')[0].split('_')
if (ids.length < 2 || ids.length > 4 || !fullPath.includes('.')) {
throw new Error('Invalid image path')
}
const quality = ids.length > 2 ? ids[2] : undefined
const fit = ids.length > 3 ? ids[3] : undefined
const parameterObject = {
imageId: ids[0],
width: ids[1].split('x')[0],
height: ids[1].split('x')[1],
quality,
format: fullPath.split('.')[1],
fit,
}
return parameterObject
}
After getting parameters on newPath
we are getting our file like below.
// classes/Image/image.ts
if (!file?.success) {
file = await rdk.getFile({
filename: imagePrefix + defaultImage,
})
}
if (!file?.success) throw new Error("Something went wrong while getting file!")
Now we will use sharp
to resize our image with given parameters. Uploaded string contains data header like this data:image/png;base64,
. We have to remove this or sharp
throws an error.
// classes/Image/image.ts
const fileData: string = file.data
const uri = fileData.split(';base64,').pop()
const image = Buffer.from(uri, 'base64')
const parameters: ResizedImageParameters = resizedImageParameters.parse({
content: image,
id: newPath.imageId,
width: newPath.width,
height: newPath.height,
quality: newPath.quality,
fit: newPath.fit,
})
Calling getResizedImage
function with parameters
.
// classes/Image/image.ts
const resizedImage = await getResizedImage(parameters)
getResizedImage
function is below. This function is outside our get
function.
// classes/Image/image.ts
const getResizedImage = async ({
height, width, quality, content, fit
} : ResizedImageParameters): Promise<Buffer> => {
const image = await sharp(content)
.resize(width, height, {
fit,
})
.toFormat('png', { quality: qualities[quality] })
.toBuffer()
return image
}
Finally returning resizedImage
in response.
// classes/Image/image.ts
data.response = {
statusCode: 200,
body: resizedImage.toString("base64"),
isBase64Encoded: true,
headers: {
"Content-Type": "image/jpg"
}
}
Remove Function
Removing image with given imageId
.
// classes/Image/image.ts
const { imageId } = data.request.body as RemoveImageInput
if (!imageId) throw new Error('Invalid remove request')
const deleteFileResponse = await rdk.deleteFile({
filename: imagePrefix + imageId,
})
if (!deleteFileResponse?.success) {
throw new Error('Image file could not removed')
}
data.response = {
statusCode: 200,
body: {
message: 'Image has been removed.',
}
}
And thats it. Here is all code for the project. Thanks.
Top comments (0)