main: added presigned url + favicon
Signed-off-by: nfel <nfilsaraee@gmail.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Client-side utility for generating pre-signed URLs from S3 keys
|
||||
*/
|
||||
|
||||
interface PresignedUrlCache {
|
||||
url: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
// Cache to avoid regenerating URLs that haven't expired
|
||||
const urlCache = new Map<string, PresignedUrlCache>();
|
||||
|
||||
/**
|
||||
* Check if a URL is an S3 key (not a full URL)
|
||||
*/
|
||||
export function isS3Key(urlOrKey: string): boolean {
|
||||
// If it starts with http:// or https://, it's already a URL
|
||||
if (urlOrKey.startsWith('http://') || urlOrKey.startsWith('https://')) {
|
||||
return false;
|
||||
}
|
||||
// Otherwise, treat it as an S3 key
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a pre-signed URL for an S3 key
|
||||
* @param key - The S3 object key
|
||||
* @param expiresIn - Expiration time in seconds (default: 1 hour)
|
||||
* @param useCache - Whether to use cached URLs (default: true)
|
||||
* @returns Pre-signed URL or the original key if it's already a URL
|
||||
*/
|
||||
export async function getPresignedUrl(
|
||||
key: string,
|
||||
expiresIn: number = 3600,
|
||||
useCache: boolean = true
|
||||
): Promise<string> {
|
||||
// If it's already a full URL, return it as-is
|
||||
if (!isS3Key(key)) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// Check cache
|
||||
if (useCache) {
|
||||
const cached = urlCache.get(key);
|
||||
if (cached && cached.expiresAt > Date.now()) {
|
||||
return cached.url;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/s3/presigned-url', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ key, expiresIn }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to generate presigned URL');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Cache the URL (expire 5 minutes before actual expiration for safety)
|
||||
if (useCache) {
|
||||
urlCache.set(key, {
|
||||
url: data.url,
|
||||
expiresAt: Date.now() + (expiresIn - 300) * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
return data.url;
|
||||
} catch (error) {
|
||||
console.error('Error generating presigned URL:', error);
|
||||
// Fallback: return the key itself (might fail, but better than nothing)
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pre-signed URLs for multiple S3 keys at once
|
||||
* @param keys - Array of S3 object keys
|
||||
* @param expiresIn - Expiration time in seconds (default: 1 hour)
|
||||
* @returns Map of key to pre-signed URL
|
||||
*/
|
||||
export async function getMultiplePresignedUrls(
|
||||
keys: string[],
|
||||
expiresIn: number = 3600
|
||||
): Promise<Record<string, string>> {
|
||||
// Separate keys that need pre-signed URLs from full URLs
|
||||
const s3Keys: string[] = [];
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
for (const key of keys) {
|
||||
if (isS3Key(key)) {
|
||||
// Check cache first
|
||||
const cached = urlCache.get(key);
|
||||
if (cached && cached.expiresAt > Date.now()) {
|
||||
result[key] = cached.url;
|
||||
} else {
|
||||
s3Keys.push(key);
|
||||
}
|
||||
} else {
|
||||
// Already a full URL
|
||||
result[key] = key;
|
||||
}
|
||||
}
|
||||
|
||||
// If no keys need pre-signed URLs, return early
|
||||
if (s3Keys.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/s3/presigned-url', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ keys: s3Keys, expiresIn }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to generate presigned URLs');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const urls = data.urls as Record<string, string>;
|
||||
|
||||
// Cache the URLs
|
||||
const cacheExpiresAt = Date.now() + (expiresIn - 300) * 1000;
|
||||
for (const [key, url] of Object.entries(urls)) {
|
||||
urlCache.set(key, { url, expiresAt: cacheExpiresAt });
|
||||
result[key] = url;
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error generating presigned URLs:', error);
|
||||
// Fallback: return the keys themselves
|
||||
for (const key of s3Keys) {
|
||||
result[key] = key;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the URL cache (useful when you want to force refresh)
|
||||
*/
|
||||
export function clearUrlCache(): void {
|
||||
urlCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear expired URLs from cache
|
||||
*/
|
||||
export function cleanupUrlCache(): void {
|
||||
const now = Date.now();
|
||||
for (const [key, cached] of urlCache.entries()) {
|
||||
if (cached.expiresAt <= now) {
|
||||
urlCache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically cleanup cache every 5 minutes
|
||||
if (typeof window !== 'undefined') {
|
||||
setInterval(cleanupUrlCache, 5 * 60 * 1000);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
|
||||
// Initialize S3 client
|
||||
@@ -17,13 +17,14 @@ export interface UploadFileParams {
|
||||
file: File;
|
||||
key: string;
|
||||
contentType?: string;
|
||||
makePublic?: boolean; // Set to false for private files
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file to S3
|
||||
*/
|
||||
export async function uploadFileToS3(params: UploadFileParams): Promise<string> {
|
||||
const { file, key, contentType } = params;
|
||||
const { file, key, contentType, makePublic = true } = params;
|
||||
|
||||
// Convert File to Buffer
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
@@ -33,14 +34,18 @@ export async function uploadFileToS3(params: UploadFileParams): Promise<string>
|
||||
Bucket: BUCKET_NAME,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ACL: "public-read",
|
||||
...(makePublic && { ACL: "public-read" }), // Only set ACL if makePublic is true
|
||||
ContentType: contentType || file.type,
|
||||
});
|
||||
|
||||
await s3Client.send(command);
|
||||
|
||||
// Return the public URL
|
||||
return `https://${BUCKET_NAME}.s3.ir-thr-at1.arvanstorage.ir/${key}`;
|
||||
// Return the public URL if public, otherwise return the key
|
||||
if (makePublic) {
|
||||
return `https://${BUCKET_NAME}.s3.ir-thr-at1.arvanstorage.ir/${key}`;
|
||||
} else {
|
||||
return key; // Return just the key for private files
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +74,41 @@ export async function getPresignedUploadUrl(key: string, contentType: string): P
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a presigned URL for downloading/viewing a file
|
||||
* @param key - The S3 object key
|
||||
* @param expiresIn - Expiration time in seconds (default: 1 hour)
|
||||
* @returns Pre-signed URL
|
||||
*/
|
||||
export async function getPresignedUrl(key: string, expiresIn: number = 3600): Promise<string> {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: BUCKET_NAME,
|
||||
Key: key,
|
||||
});
|
||||
|
||||
const url = await getSignedUrl(s3Client, command, { expiresIn });
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate multiple presigned URLs for a list of keys
|
||||
* @param keys - Array of S3 object keys
|
||||
* @param expiresIn - Expiration time in seconds (default: 1 hour)
|
||||
* @returns Map of key to pre-signed URL
|
||||
*/
|
||||
export async function getMultiplePresignedUrls(
|
||||
keys: string[],
|
||||
expiresIn: number = 3600
|
||||
): Promise<Record<string, string>> {
|
||||
const urlPromises = keys.map(async (key) => {
|
||||
const url = await getPresignedUrl(key, expiresIn);
|
||||
return [key, url];
|
||||
});
|
||||
|
||||
const entries = await Promise.all(urlPromises);
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique key for file uploads
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user