Skip to content

Commit 22265cb

Browse files
committed
Added: MVP
1 parent 8bbc829 commit 22265cb

File tree

6 files changed

+83
-53
lines changed

6 files changed

+83
-53
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
NODE_ENV=development
2-
AWS_S3_REGION=us-east-1
2+
NEXT_PUBLIC_AWS_S3_REGION=us-east-1
33
NEXT_PUBLIC_AWS_S3_BUCKET_NAME=your-bucket-name
44
AWS_ACCESS_KEY_ID=key
55
AWS_SECRET_ACCESS_KEY=secret

src/app/api/delete-objects/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { S3Client, DeleteObjectsCommand } from "@aws-sdk/client-s3";
22
import { NextResponse } from "next/server";
33

44
const s3Client = new S3Client({
5-
region: process.env.AWS_S3_REGION,
5+
region: process.env.NEXT_PUBLIC_AWS_S3_REGION,
66
credentials: {
77
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
88
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,

src/app/api/objects/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
22
import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3";
33

44
const s3Client = new S3Client({
5-
region: process.env.AWS_S3_REGION, // Ensure this is set in your environment variables
5+
region: process.env.NEXT_PUBLIC_AWS_S3_REGION, // Ensure this is set in your environment variables
66
credentials: {
77
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
88
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,

src/app/api/upload/route.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface ImageUploadInfo {
77
format: string;
88
previewUrl: string;
99
bucket: string;
10+
folder: string;
1011
}
1112

1213
export async function POST(req: NextRequest) {
@@ -47,10 +48,17 @@ async function uploadImageToS3(
4748

4849
if (imageUploadInfo.bucket === "server") {
4950
bucketName = process.env.NEXT_PUBLIC_AWS_S3_BUCKET_NAME;
50-
key = `assets/${imageUploadInfo.title}`;
51+
key = `assets/${imageUploadInfo.folder}/${imageUploadInfo.title}`.replace(
52+
/\/\//g,
53+
"/",
54+
);
5155
} else {
5256
bucketName = process.env.NEXT_PUBLIC_AWS_S3_STORAGE_BUCKET_NAME;
53-
key = `images/assets/${imageUploadInfo.title}`;
57+
key =
58+
`images/assets/${imageUploadInfo.folder}/${imageUploadInfo.title}`.replace(
59+
/\/\//g,
60+
"/",
61+
);
5462
}
5563

5664
const uploadParams = {

src/components/object-list.tsx

Lines changed: 69 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useState } from "react";
4-
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
4+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
55
import { toast } from "sonner";
66
import { cn } from "@/lib/utils";
77

@@ -32,7 +32,7 @@ export default function ObjectList({
3232
const [currentPage, setCurrentPage] = useState(1);
3333
const [selectedObjects, setSelectedObjects] = useState<string[]>([]);
3434
const itemsPerPage = 100;
35-
const s3Region = process.env.AWS_S3_REGION;
35+
const s3Region = process.env.NEXT_PUBLIC_AWS_S3_REGION;
3636

3737
const queryClient = useQueryClient();
3838

@@ -67,11 +67,18 @@ export default function ObjectList({
6767
return keys;
6868
},
6969
onSuccess: (deletedKeys) => {
70-
queryClient.setQueryData<S3ObjectData>(["objects", type], (oldData) => ({
71-
objects: oldData?.objects.filter(
72-
(object) => !deletedKeys.includes(object.key),
73-
),
74-
}));
70+
queryClient.setQueryData<S3ObjectData>(["objects", type], (oldData) => {
71+
if (!oldData) {
72+
return { objects: [] };
73+
}
74+
75+
return {
76+
...oldData,
77+
objects: oldData.objects.filter(
78+
(object) => !deletedKeys.includes(object.key),
79+
),
80+
};
81+
});
7582
toast.success("Selected objects deleted successfully!");
7683
setSelectedObjects([]);
7784
},
@@ -164,29 +171,20 @@ export default function ObjectList({
164171

165172
const totalPages = Math.ceil(filteredObjects.length / itemsPerPage);
166173

167-
const uniqueFolders = Array.from(
168-
new Set(
169-
objects.map((object) => {
170-
const segments = object.key.split("/");
171-
if (segments[0] === "assets") {
172-
if (segments[1] === "images" && segments.length > 2) {
173-
return `assets/${segments[2]}`;
174-
} else if (segments[1] !== "images") {
175-
return null;
176-
}
177-
}
178-
return segments[0];
179-
}),
180-
),
181-
).filter((folder) => folder !== null);
174+
const folders = filterObjectsByPrefix(objects, type);
182175

183176
const extractPath = (url: string) => {
184177
const match = /https?:\/\/[^\/]+(\/.*)$/.exec(url);
185178
return match ? match[1] : url;
186179
};
187180

188-
const generateS3Link = (key: string) => {
189-
return `https://${s3Region}.console.aws.amazon.com/s3/object/blazing-peon-images?region=${s3Region}&bucketType=general&prefix=${key}`;
181+
const generateS3Link = (key: string, type: string) => {
182+
const bucket =
183+
type === "server"
184+
? process.env.NEXT_PUBLIC_AWS_S3_BUCKET_NAME
185+
: process.env.NEXT_PUBLIC_AWS_S3_STORAGE_BUCKET_NAME;
186+
187+
return `https://${s3Region}.console.aws.amazon.com/s3/object/${bucket}?region=${s3Region}&bucketType=general&prefix=${key}`;
190188
};
191189

192190
return (
@@ -204,8 +202,7 @@ export default function ObjectList({
204202
onChange={handleFolderChange}
205203
className="ml-2 rounded border border-border bg-inputBg p-2"
206204
>
207-
<option value="">All Folders</option>
208-
{uniqueFolders.map((folder, index) => (
205+
{folders.map((folder, index) => (
209206
<option key={index} value={folder}>
210207
{folder}
211208
</option>
@@ -234,6 +231,17 @@ export default function ObjectList({
234231
}
235232
/>
236233
</th>
234+
<th
235+
className="cursor-pointer pb-2 text-textPrimary"
236+
onClick={() => handleSort("s3Object")}
237+
>
238+
S3 Object{" "}
239+
{sortOrder.column === "s3Object"
240+
? sortOrder.order === "asc"
241+
? "▲"
242+
: "▼"
243+
: ""}
244+
</th>
237245
{type === "server" && (
238246
<th
239247
className="cursor-pointer pb-2 text-textPrimary"
@@ -247,17 +255,6 @@ export default function ObjectList({
247255
: ""}
248256
</th>
249257
)}
250-
<th
251-
className="cursor-pointer pb-2 text-textPrimary"
252-
onClick={() => handleSort("s3Object")}
253-
>
254-
S3 Object{" "}
255-
{sortOrder.column === "s3Object"
256-
? sortOrder.order === "asc"
257-
? "▲"
258-
: "▼"
259-
: ""}
260-
</th>
261258
<th
262259
className="w-[80px] cursor-pointer pb-2 text-textPrimary"
263260
onClick={() => handleSort("size")}
@@ -292,6 +289,18 @@ export default function ObjectList({
292289
onChange={() => handleCheckboxChange(object.key)}
293290
/>
294291
</td>
292+
<td className="border-t border-border py-2">
293+
<a
294+
href={generateS3Link(object.key, type)}
295+
target="_blank"
296+
rel="noopener noreferrer"
297+
className="text-link hover:underline"
298+
>
299+
{type === "server"
300+
? object.key.split("/").pop()
301+
: object.key}
302+
</a>
303+
</td>
295304
{type === "server" && (
296305
<td className="border-t border-border py-2">
297306
<a
@@ -304,16 +313,6 @@ export default function ObjectList({
304313
</a>
305314
</td>
306315
)}
307-
<td className="border-t border-border py-2">
308-
<a
309-
href={generateS3Link(object.key)}
310-
target="_blank"
311-
rel="noopener noreferrer"
312-
className="text-link text-blue-500 hover:underline"
313-
>
314-
{object.key.split("/").pop()}
315-
</a>
316-
</td>
317316
<td className="border-t border-border py-2 text-textPrimary">
318317
{object.sizeInKB} KB
319318
</td>
@@ -362,3 +361,26 @@ export default function ObjectList({
362361
</div>
363362
);
364363
}
364+
365+
function filterObjectsByPrefix(
366+
objects: S3Object[],
367+
type: "server" | "storage",
368+
): string[] {
369+
const prefix = type === "server" ? "assets/" : "images/assets/";
370+
371+
// Ensure uniqueness
372+
return objects
373+
.filter((obj) => obj.key.startsWith(prefix))
374+
.map((obj) => {
375+
// Remove prefix
376+
const path = obj.key;
377+
// Remove the file name (anything after the last '/')
378+
const lastSlashIndex = path.lastIndexOf("/");
379+
if (lastSlashIndex === -1) {
380+
return ""; // Return empty string if no folder structure exists
381+
}
382+
return path.substring(0, lastSlashIndex);
383+
})
384+
.filter((folder) => folder !== "") // Remove empty strings
385+
.filter((folder, index, self) => self.indexOf(folder) === index);
386+
}

src/lib/s3-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { S3Client } from "@aws-sdk/client-s3";
22

33
export const s3Client = new S3Client({
4-
region: process.env.AWS_S3_REGION,
4+
region: process.env.NEXT_PUBLIC_AWS_S3_REGION,
55
credentials: {
66
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
77
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,

0 commit comments

Comments
 (0)