Starting adding S3 support for file uploading and retrieving.

This commit is contained in:
charles-gauthereau
2024-11-11 20:29:19 +01:00
parent 95f6af33ae
commit 9bafcce15a
5 changed files with 348 additions and 6 deletions
+14 -2
View File
@@ -16,8 +16,14 @@ export const env = createEnv({
SMTP_USER: z.string(),
NEXT_PUBLIC_SECRET: z.string(),
NEXTAUTH_URL: z.string(),
AUTH_GOOGLE_ID: z.string(),
AUTH_GOOGLE_SECRET: z.string(),
AUTH_GOOGLE_ID: z.string().optional(),
AUTH_GOOGLE_SECRET: z.string().optional(),
S3_ENDPOINT: z.string().optional(),
S3_ACCESS_KEY: z.string().optional(),
S3_SECRET_KEY: z.string().optional(),
S3_BUCKET_NAME: z.string().optional(),
S3_PORT: z.string().optional(),
S3_USE_SSL: z.string().optional(),
},
/*
* Environment variables available on the client (and server).
@@ -46,5 +52,11 @@ export const env = createEnv({
SMTP_USER: process.env.SMTP_USER,
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
S3_ENDPOINT: process.env.S3_ENDPOINT,
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
S3_BUCKET_NAME: process.env.S3_BUCKET_NAME,
S3_PORT: process.env.S3_PORT,
S3_USE_SSL: process.env.S3_USE_SSL,
},
});
+10
View File
@@ -0,0 +1,10 @@
import {format} from "date-fns";
export function humanReadableDate(rawDate: string | number | Date) {
return format(new Date(rawDate), 'dd/MM/yyyy HH:mm')
}
export function timeAgo(rawDate: string | number | Date) {
const date = new Date(rawDate)
return "Not implemented"
}
+143
View File
@@ -0,0 +1,143 @@
import * as Minio from 'minio'
import {env} from "@/env.mjs";
import internal from "node:stream";
// Create a new Minio client with the S3 endpoint, access key, and secret key
export const s3Client = env.NODE_ENV === "production" ?
new Minio.Client({
endPoint: env.S3_ENDPOINT ?? "",
accessKey: env.S3_ACCESS_KEY ?? "",
secretKey: env.S3_SECRET_KEY ?? "",
}) : new Minio.Client({
endPoint: env.S3_ENDPOINT ?? "",
port: Number(env.S3_PORT ?? 0),
accessKey: env.S3_ACCESS_KEY ?? "",
secretKey: env.S3_SECRET_KEY ?? "",
useSSL: env.S3_USE_SSL === 'true'
})
export async function checkMinioAlive() {
try {
// Try to list buckets to check connectivity
const buckets = await s3Client.listBuckets();
console.log('MinIO is up and running. Buckets:', buckets);
} catch (error) {
console.error('Error connecting to MinIO:', error);
}
}
export async function createBucketIfNotExists(bucketName: string) {
const bucketExists = await s3Client.bucketExists(bucketName)
if (!bucketExists) {
console.log(`Creating bucket ${bucketName}`);
await s3Client.makeBucket(bucketName)
}
}
/**
* Save file in S3 bucket
* @param bucketName name of the bucket
* @param fileName name of the file
* @param file file to save
*/
export async function saveFileInBucket({
bucketName,
fileName,
file,
}: {
bucketName: string
fileName: string
file: Buffer | internal.Readable
}) {
// Check if Minio is Alive
await checkMinioAlive()
// Create bucket if it doesn't exist
await createBucketIfNotExists(bucketName)
// check if file exists - optional.
// Without this check, the file will be overwritten if it exists
const fileExists = await checkFileExistsInBucket({
bucketName,
fileName,
})
console.log('File exists:', fileExists);
if (fileExists) {
throw new Error('File already exists')
}
// Upload image to S3 bucket
const result = await s3Client.putObject(bucketName, fileName, file)
return result
}
/**
* Check if file exists in bucket
* @param bucketName name of the bucket
* @param fileName name of the file
* @returns true if file exists, false if not
*/
export async function checkFileExistsInBucket({bucketName, fileName}: { bucketName: string; fileName: string }) {
try {
await s3Client.statObject(bucketName, fileName)
} catch (error) {
return false
}
return true
}
/**
* Generate presigned urls for uploading files to S3
* @param files files to upload
* @returns promise with array of presigned urls
*/
export async function createPresignedUrlToUpload({
bucketName,
fileName,
expiry = 60 * 60, // 1 hour
}: {
bucketName: string
fileName: string
expiry?: number
}) {
// Create bucket if it doesn't exist
await createBucketIfNotExists(bucketName)
return await s3Client.presignedPutObject(bucketName, fileName, expiry)
}
// Function to create a bucket and make it public
export async function createPublicBucket({bucketName}: { bucketName: string }) {
try {
// Check if the bucket already exists
const exists = await s3Client.bucketExists(bucketName);
if (!exists) {
// Create the bucket
await s3Client.makeBucket(bucketName); // Change region if needed
console.log(`Bucket ${bucketName} created successfully.`);
} else {
console.log(`Bucket ${bucketName} already exists.`);
}
// Define a bucket policy for public access
const policy = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: "*",
Action: 's3:GetObject',
Resource: `arn:aws:s3:::${bucketName}/*`
}
]
};
// Set the policy to the bucket
await s3Client.setBucketPolicy(bucketName, JSON.stringify(policy));
console.log(`Bucket ${bucketName} is now public.`);
} catch (error) {
console.error('Error creating bucket:', error);
}
}