Skip to content

Commit 625192b

Browse files
committed
Added: support for uploading multiple images and better object listing
1 parent 618aace commit 625192b

File tree

6 files changed

+751
-213
lines changed

6 files changed

+751
-213
lines changed

src/app/api/optimize/route.ts

Lines changed: 140 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,150 @@
11
import { NextResponse } from "next/server";
2-
import sharp from "sharp";
2+
import sharp, { type Sharp, type ResizeOptions } from "sharp";
33

4-
export async function POST(request: Request) {
4+
interface ProcessedFile {
5+
buffer: Buffer;
6+
contentType: string;
7+
originalName: string;
8+
}
9+
10+
export async function POST(request: Request): Promise<NextResponse> {
511
const formData = await request.formData();
6-
const file = formData.get("image") as Blob;
7-
const quality = Number(formData.get("quality"));
8-
const width = formData.get("width") ? Number(formData.get("width")) : null;
9-
const height = formData.get("height") ? Number(formData.get("height")) : null;
10-
11-
// Ensure at least one of width or height is provided
12-
if (!width && !height) {
13-
return new NextResponse("Either width or height must be provided", {
14-
status: 400,
12+
const files = formData.getAll("image") as Blob[];
13+
const quality = parseNumber(formData.get("quality"));
14+
const width = parseNumber(formData.get("width"));
15+
const height = parseNumber(formData.get("height"));
16+
const format = formData.get("format") as string | null;
17+
18+
const processedFiles: ProcessedFile[] = [];
19+
20+
for (const file of files) {
21+
const buffer = Buffer.from(await file.arrayBuffer());
22+
const originalFormat = file.type;
23+
const originalName = (file as unknown as { name: string }).name; // Type assertion to get the file name
24+
25+
// Determine if processing is necessary
26+
const shouldProcessImage =
27+
!width &&
28+
!height &&
29+
!quality &&
30+
(format === "preserve" ||
31+
format === getFormatFromMimeType(originalFormat));
32+
33+
if (shouldProcessImage) {
34+
processedFiles.push({
35+
buffer,
36+
contentType: originalFormat,
37+
originalName,
38+
});
39+
continue;
40+
}
41+
42+
let optimizedImage = sharp(buffer);
43+
44+
// Apply resizing if needed
45+
if (width || height) {
46+
const resizeOptions: ResizeOptions = { width, height };
47+
optimizedImage = optimizedImage.resize(resizeOptions);
48+
}
49+
50+
// Apply format and quality adjustments
51+
optimizedImage = applyFormatAndQuality(
52+
optimizedImage,
53+
format,
54+
quality,
55+
originalFormat,
56+
);
57+
58+
const finalBuffer = await optimizedImage.toBuffer();
59+
const contentType = determineContentType(format, originalFormat);
60+
const finalName =
61+
format === "preserve" || format === getFormatFromMimeType(originalFormat)
62+
? originalName
63+
: originalName.replace(/\.[^/.]+$/, `.${contentType.split("/")[1]}`);
64+
65+
processedFiles.push({
66+
buffer: finalBuffer,
67+
contentType,
68+
originalName: finalName,
1569
});
1670
}
1771

18-
const buffer = Buffer.from(await file.arrayBuffer());
72+
return NextResponse.json(
73+
processedFiles.map((file) => ({
74+
buffer: file.buffer.toString("base64"), // Convert buffer to base64 for transmission
75+
contentType: file.contentType,
76+
originalName: file.originalName,
77+
})),
78+
);
79+
}
1980

20-
const resizeOptions: sharp.ResizeOptions = {};
21-
if (width) resizeOptions.width = width;
22-
if (height) resizeOptions.height = height;
81+
function parseNumber(value: FormDataEntryValue | null): number | undefined {
82+
return value ? Number(value) : undefined;
83+
}
84+
85+
function applyFormatAndQuality(
86+
image: Sharp,
87+
format: string | null,
88+
quality: number | undefined,
89+
originalFormat: string,
90+
): Sharp {
91+
if (
92+
format === "preserve" ||
93+
format === getFormatFromMimeType(originalFormat)
94+
) {
95+
switch (originalFormat) {
96+
case "image/webp":
97+
return image.webp({ quality });
98+
case "image/jpeg":
99+
return image.jpeg({ quality });
100+
case "image/png":
101+
return image.png({ quality });
102+
default:
103+
return image;
104+
}
105+
}
23106

24-
const optimizedImage = await sharp(buffer)
25-
.resize(resizeOptions)
26-
.webp({ quality })
27-
.toBuffer();
107+
switch (format) {
108+
case "webp":
109+
return image.webp({ quality });
110+
case "jpg":
111+
return image.jpeg({ quality });
112+
case "png":
113+
return image.png({ quality });
114+
default:
115+
return image;
116+
}
117+
}
28118

29-
return new NextResponse(optimizedImage, {
30-
headers: { "Content-Type": "image/webp" },
31-
});
119+
function determineContentType(
120+
format: string | null,
121+
originalType: string,
122+
): string {
123+
if (format === "preserve") {
124+
return originalType;
125+
}
126+
127+
switch (format) {
128+
case "webp":
129+
return "image/webp";
130+
case "jpg":
131+
return "image/jpeg";
132+
case "png":
133+
return "image/png";
134+
default:
135+
return originalType;
136+
}
137+
}
138+
139+
function getFormatFromMimeType(mimeType: string): string {
140+
switch (mimeType) {
141+
case "image/webp":
142+
return "webp";
143+
case "image/jpeg":
144+
return "jpg";
145+
case "image/png":
146+
return "png";
147+
default:
148+
return "unknown";
149+
}
32150
}

src/app/api/upload/route.ts

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,40 @@ const s3Client = new S3Client({
1111

1212
export async function POST(request: Request) {
1313
const formData = await request.formData();
14-
const file = formData.get("image") as Blob;
15-
const imageName = formData.get("imageName") as string;
14+
const files = formData.getAll("image") as Blob[];
15+
const imageNames = formData.getAll("imageName") as string[];
16+
const folder = formData.get("folder") as string;
1617

17-
const buffer = Buffer.from(await file.arrayBuffer());
18+
if (!files.length || !imageNames.length || !folder) {
19+
return new NextResponse("Missing required fields", { status: 400 });
20+
}
1821

19-
const uploadParams = {
20-
Bucket: process.env.AWS_S3_BUCKET_NAME!,
21-
Key: `assets/images/${imageName}`,
22-
Body: buffer,
23-
ContentType: "image/webp",
24-
CacheControl: "max-age=31536000",
25-
};
22+
const uploadedFiles: { imageName: string; imageUrl: string }[] = [];
2623

27-
await s3Client.send(new PutObjectCommand(uploadParams));
24+
for (let i = 0; i < files.length; i++) {
25+
const file = files[i];
26+
const imageName = imageNames[i];
2827

29-
const imageUrl = `https://${process.env.AWS_CLOUDFRONT_DISTRIBUTION}.cloudfront.net/assets/images/${imageName}`;
28+
if (!file || !imageName) {
29+
continue; // Skip if either file or imageName is undefined
30+
}
3031

31-
return NextResponse.json({
32-
imageName: `assets/images/${imageName}`,
33-
imageUrl,
34-
});
32+
const buffer = Buffer.from(await file.arrayBuffer());
33+
34+
const uploadParams = {
35+
Bucket: process.env.AWS_S3_BUCKET_NAME!,
36+
Key: `assets/images/${folder}/${imageName}`,
37+
Body: buffer,
38+
ContentType: file.type,
39+
CacheControl: "max-age=31536000",
40+
};
41+
42+
await s3Client.send(new PutObjectCommand(uploadParams));
43+
44+
const imageUrl = `https://${process.env.AWS_CLOUDFRONT_DISTRIBUTION}.cloudfront.net/assets/images/${folder}/${imageName}`;
45+
46+
uploadedFiles.push({ imageName: `${folder}/${imageName}`, imageUrl });
47+
}
48+
49+
return NextResponse.json(uploadedFiles);
3550
}

0 commit comments

Comments
 (0)