adding image upload

This commit is contained in:
zmeyer44 2023-10-18 09:05:59 -04:00
parent 593bb467ad
commit e4879ef639
7 changed files with 174 additions and 0 deletions

7
.env Normal file
View File

@ -0,0 +1,7 @@
# S3 data storage
S3_BUCKET_NAME="flockstr"
MY_AWS_ACCESS_KEY="AKIAT2UDOJMC6K25RFFN"
MY_AWS_SECRET_KEY="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"
REGION="us-east-1"
S3_BUCKET_URL="https://flockstr.s3.amazonaws.com"
NEXT_PUBLIC_S3_BUCKET_URL="https://flockstr.s3.amazonaws.com"

28
app/api/upload/route.ts Normal file
View File

@ -0,0 +1,28 @@
import { nanoid } from "nanoid";
import { NextResponse } from "next/server";
import { generateV4UploadSignedUrl } from "@/lib/actions/upload";
import { z } from "zod";
const BodySchema = z.object({
folderName: z.string().optional(),
fileType: z.string(),
});
// export const runtime = "edge";
async function handler(req: Request) {
// const session = await getSession();
// if (!session?.user.id) {
// return new Response("Unauthorized", {
// status: 401,
// });
// }
const rawJson = await req.json();
const body = BodySchema.parse(rawJson);
const { folderName, fileType } = body;
const filename = (`${folderName}/` ?? "") + nanoid();
const signedUrl = await generateV4UploadSignedUrl(filename, fileType);
return NextResponse.json({ ...signedUrl, fileName: filename });
}
export { handler as POST };

BIN
bun.lockb

Binary file not shown.

19
lib/actions/upload.ts Normal file
View File

@ -0,0 +1,19 @@
import { s3Client } from "@/lib/clients/s3";
export async function generateV4UploadSignedUrl(
fileName: string,
fileType: string,
) {
const preSignedUrl = await s3Client.getSignedUrl("putObject", {
Bucket: process.env.S3_BUCKET_NAME,
Key: fileName,
ContentType: fileType,
Expires: 5 * 60,
});
console.log("PresignedUrl", preSignedUrl);
return { url: preSignedUrl };
}
export default generateV4UploadSignedUrl;

9
lib/clients/s3.ts Normal file
View File

@ -0,0 +1,9 @@
import S3 from "aws-sdk/clients/s3";
const s3Client = new S3({
signatureVersion: "v4",
region: process.env.REGION,
accessKeyId: process.env.MY_AWS_ACCESS_KEY,
secretAccessKey: process.env.MY_AWS_SECRET_KEY,
});
export { s3Client };

View File

@ -0,0 +1,110 @@
"use client";
import { useState, ReactNode, useRef } from "react";
import { z } from "zod";
import { createZodFetcher } from "zod-fetch";
const fetchWithZod = createZodFetcher();
const PresignedPostSchema = z.object({
url: z.string(),
fileName: z.string(),
});
const useImageUpload = (folderName?: string) => {
const [status, setStatus] = useState<
"empty" | "uploading" | "success" | "error"
>("empty");
const [imageUrl, setImageUrl] = useState<string | null>();
const [imagePreview, setImagePreview] = useState<string | null>();
const uploadImage = async (file: File, folderName?: string) => {
if (!file) return;
try {
const presignedPost = await fetchWithZod(
// The schema you want to validate with
PresignedPostSchema,
// Any parameters you would usually pass to fetch
"/api/upload",
{
method: "POST",
body: JSON.stringify({ folderName, fileType: file.type }),
},
);
const { url, fileName } = presignedPost;
if (!url) return;
const result = await fetch(url, {
method: "PUT",
body: file,
headers: {
"Content-Type": file.type,
},
});
if (result.ok) {
setStatus("success");
const imageUrl = `${process.env.NEXT_PUBLIC_S3_BUCKET_URL}/${fileName}`;
setImageUrl(imageUrl);
setImagePreview(imageUrl);
return imageUrl;
}
return;
} catch (err) {
setStatus("error");
console.log("ERROR", err);
}
};
const onImageChange = (e: React.FormEvent<HTMLInputElement>) => {
const file = e.currentTarget.files?.[0];
if (!file) return;
setStatus("uploading");
uploadImage(file, folderName);
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = async (readerEvent) => {
setImagePreview(readerEvent?.target?.result as string);
};
};
const ImageUploadButton = ({ children }: { children: ReactNode }) => {
const inputFileRef = useRef<HTMLInputElement>(null);
function onButtonClick() {
if (inputFileRef.current) {
inputFileRef.current!.click();
}
}
return (
<>
<button type="button" onClick={onButtonClick}>
{children}
</button>
<input
type="file"
accept="image/png, image/jpeg"
hidden
onChange={onImageChange}
ref={inputFileRef}
/>
</>
);
};
const clear = () => {
setStatus("empty");
setImageUrl(null);
setImagePreview(null);
};
return {
imagePreview,
status,
imageUrl,
ImageUploadButton,
clear,
};
};
export default useImageUpload;

View File

@ -29,6 +29,7 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@tailwindcss/container-queries": "^0.1.1",
"aws-sdk": "^2.1475.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",