优化图文页
This commit is contained in:
+6
-3
@@ -1,10 +1,13 @@
|
||||
// lib/json.ts
|
||||
export function json(data: unknown, init?: ResponseInit) {
|
||||
return new Response(
|
||||
JSON.stringify(data, (_k, v) => (typeof v === 'bigint' ? v.toString() : v)),
|
||||
JSON.stringify(data, (_k, v) => (typeof v === "bigint" ? v.toString() : v)),
|
||||
{
|
||||
...init,
|
||||
headers: { 'content-type': 'application/json; charset=utf-8', ...(init?.headers || {}) },
|
||||
}
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
...(init?.headers || {}),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+52
-52
@@ -14,7 +14,7 @@ import {
|
||||
downloadFile,
|
||||
fileExists,
|
||||
getFileInfo,
|
||||
} from './minio';
|
||||
} from "./minio";
|
||||
|
||||
// ========================================
|
||||
// 1. 上传文件示例
|
||||
@@ -25,15 +25,15 @@ import {
|
||||
*/
|
||||
async function uploadAvatar(file: File, userId: string) {
|
||||
// 验证文件类型
|
||||
const allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
const allowedTypes = ["jpg", "jpeg", "png", "gif", "webp"];
|
||||
if (!validateFileType(file.name, allowedTypes)) {
|
||||
throw new Error('不支持的图片格式');
|
||||
throw new Error("不支持的图片格式");
|
||||
}
|
||||
|
||||
// 验证文件大小(5MB)
|
||||
const maxSize = 5 * 1024 * 1024;
|
||||
if (!validateFileSize(file.size, maxSize)) {
|
||||
throw new Error('文件大小超过限制(最大5MB)');
|
||||
throw new Error("文件大小超过限制(最大5MB)");
|
||||
}
|
||||
|
||||
// 生成唯一文件名,存储在 avatars 目录下
|
||||
@@ -41,8 +41,8 @@ async function uploadAvatar(file: File, userId: string) {
|
||||
|
||||
// 上传文件
|
||||
const url = await uploadFile(file, path, {
|
||||
'Content-Type': file.type,
|
||||
'User-Id': userId,
|
||||
"Content-Type": file.type,
|
||||
"User-Id": userId,
|
||||
});
|
||||
|
||||
return { url, path };
|
||||
@@ -52,28 +52,28 @@ async function uploadAvatar(file: File, userId: string) {
|
||||
* 上传文章封面图
|
||||
*/
|
||||
async function uploadPostCover(file: File, postId: string) {
|
||||
const allowedTypes = ['jpg', 'jpeg', 'png', 'webp'];
|
||||
const allowedTypes = ["jpg", "jpeg", "png", "webp"];
|
||||
if (!validateFileType(file.name, allowedTypes)) {
|
||||
throw new Error('不支持的图片格式');
|
||||
throw new Error("不支持的图片格式");
|
||||
}
|
||||
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (!validateFileSize(file.size, maxSize)) {
|
||||
throw new Error('文件大小超过限制(最大10MB)');
|
||||
throw new Error("文件大小超过限制(最大10MB)");
|
||||
}
|
||||
|
||||
// 按日期组织文件
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
|
||||
const path = generateUniqueFileName(
|
||||
file.name,
|
||||
`posts/${year}/${month}/covers`
|
||||
`posts/${year}/${month}/covers`,
|
||||
);
|
||||
|
||||
const url = await uploadFile(file, path);
|
||||
|
||||
|
||||
return { url, path };
|
||||
}
|
||||
|
||||
@@ -81,27 +81,27 @@ async function uploadPostCover(file: File, postId: string) {
|
||||
* 上传文章内容中的图片
|
||||
*/
|
||||
async function uploadPostImage(file: File, postId: string) {
|
||||
const allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
const allowedTypes = ["jpg", "jpeg", "png", "gif", "webp"];
|
||||
if (!validateFileType(file.name, allowedTypes)) {
|
||||
throw new Error('不支持的图片格式');
|
||||
throw new Error("不支持的图片格式");
|
||||
}
|
||||
|
||||
const maxSize = 5 * 1024 * 1024; // 5MB
|
||||
if (!validateFileSize(file.size, maxSize)) {
|
||||
throw new Error('文件大小超过限制(最大5MB)');
|
||||
throw new Error("文件大小超过限制(最大5MB)");
|
||||
}
|
||||
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
|
||||
const path = generateUniqueFileName(
|
||||
file.name,
|
||||
`posts/${year}/${month}/images`
|
||||
`posts/${year}/${month}/images`,
|
||||
);
|
||||
|
||||
const url = await uploadFile(file, path);
|
||||
|
||||
|
||||
return { url, path };
|
||||
}
|
||||
|
||||
@@ -126,9 +126,9 @@ function getPublicFileUrl(path: string) {
|
||||
async function deleteAvatar(avatarPath: string) {
|
||||
try {
|
||||
await deleteFile(avatarPath);
|
||||
console.log('头像删除成功');
|
||||
console.log("头像删除成功");
|
||||
} catch (error) {
|
||||
console.error('删除头像失败:', error);
|
||||
console.error("删除头像失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -140,22 +140,22 @@ async function deletePostImages(postId: string) {
|
||||
try {
|
||||
// 列出文章相关的所有图片
|
||||
const files = await listFiles(`posts/`, true);
|
||||
|
||||
|
||||
// 过滤出该文章的图片(根据实际情况调整逻辑)
|
||||
const postFiles = files.filter(file =>
|
||||
file.name?.includes(postId)
|
||||
);
|
||||
const postFiles = files.filter((file) => file.name?.includes(postId));
|
||||
|
||||
// 批量删除
|
||||
const paths = postFiles.map(f => f.name).filter((name): name is string => !!name);
|
||||
|
||||
const paths = postFiles
|
||||
.map((f) => f.name)
|
||||
.filter((name): name is string => !!name);
|
||||
|
||||
if (paths.length > 0) {
|
||||
const { deleteFiles } = await import('./minio');
|
||||
const { deleteFiles } = await import("./minio");
|
||||
await deleteFiles(paths);
|
||||
console.log(`删除了 ${paths.length} 个文件`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除文章图片失败:', error);
|
||||
console.error("删除文章图片失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -170,15 +170,15 @@ async function deletePostImages(postId: string) {
|
||||
async function getUserAvatars(userId: string) {
|
||||
try {
|
||||
const files = await listFiles(`avatars/${userId}/`, false);
|
||||
|
||||
return files.map(file => ({
|
||||
|
||||
return files.map((file) => ({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
lastModified: file.lastModified,
|
||||
url: file.name ? getFileUrl(file.name) : null,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('获取用户头像列表失败:', error);
|
||||
console.error("获取用户头像列表失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -188,16 +188,16 @@ async function getUserAvatars(userId: string) {
|
||||
*/
|
||||
async function getPostCoversByMonth(year: number, month: number) {
|
||||
try {
|
||||
const monthStr = String(month).padStart(2, '0');
|
||||
const monthStr = String(month).padStart(2, "0");
|
||||
const files = await listFiles(`posts/${year}/${monthStr}/covers/`, false);
|
||||
|
||||
return files.map(file => ({
|
||||
|
||||
return files.map((file) => ({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
url: file.name ? getFileUrl(file.name) : null,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('获取封面列表失败:', error);
|
||||
console.error("获取封面列表失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -223,15 +223,15 @@ async function checkAvatarExists(avatarPath: string): Promise<boolean> {
|
||||
async function getFileDetails(path: string) {
|
||||
try {
|
||||
const info = await getFileInfo(path);
|
||||
|
||||
|
||||
return {
|
||||
size: info.size,
|
||||
lastModified: info.lastModified,
|
||||
etag: info.etag,
|
||||
contentType: info.metaData?.['content-type'],
|
||||
contentType: info.metaData?.["content-type"],
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取文件信息失败:', error);
|
||||
console.error("获取文件信息失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -247,7 +247,7 @@ async function downloadFileToBuffer(path: string): Promise<Buffer> {
|
||||
try {
|
||||
return await downloadFile(path);
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
console.error("下载文件失败:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -258,42 +258,42 @@ async function downloadFileToBuffer(path: string): Promise<Buffer> {
|
||||
|
||||
/**
|
||||
* Next.js API Route 示例:上传文件
|
||||
*
|
||||
*
|
||||
* 使用方法:
|
||||
*
|
||||
*
|
||||
* // app/api/upload/route.ts
|
||||
* import { uploadFile, generateUniqueFileName } from '@/lib/minio';
|
||||
*
|
||||
*
|
||||
* export async function POST(request: Request) {
|
||||
* const formData = await request.formData();
|
||||
* const file = formData.get('file') as File;
|
||||
*
|
||||
*
|
||||
* if (!file) {
|
||||
* return Response.json({ error: '没有文件' }, { status: 400 });
|
||||
* }
|
||||
*
|
||||
*
|
||||
* const path = generateUniqueFileName(file.name, 'uploads');
|
||||
* const url = await uploadFile(file, path);
|
||||
*
|
||||
*
|
||||
* return Response.json({ url, path });
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Next.js API Route 示例:删除文件
|
||||
*
|
||||
*
|
||||
* // app/api/delete/route.ts
|
||||
* import { deleteFile } from '@/lib/minio';
|
||||
*
|
||||
*
|
||||
* export async function DELETE(request: Request) {
|
||||
* const { path } = await request.json();
|
||||
*
|
||||
*
|
||||
* if (!path) {
|
||||
* return Response.json({ error: '缺少文件路径' }, { status: 400 });
|
||||
* }
|
||||
*
|
||||
*
|
||||
* await deleteFile(path);
|
||||
*
|
||||
*
|
||||
* return Response.json({ success: true });
|
||||
* }
|
||||
*/
|
||||
|
||||
+69
-50
@@ -1,22 +1,22 @@
|
||||
import * as Minio from 'minio';
|
||||
import * as Minio from "minio";
|
||||
|
||||
// MinIO 客户端配置
|
||||
const useSSL = process.env.MINIO_USE_SSL === 'true';
|
||||
const useSSL = process.env.MINIO_USE_SSL === "true";
|
||||
const port = Number(process.env.MINIO_PORT) || 9000;
|
||||
|
||||
// 当使用标准HTTPS端口(443)或HTTP端口(80)时,MinIO客户端不需要指定端口
|
||||
const shouldOmitPort = (useSSL && port === 443) || (!useSSL && port === 80);
|
||||
|
||||
const minioClient = new Minio.Client({
|
||||
endPoint: process.env.MINIO_ENDPOINT || 'localhost',
|
||||
endPoint: process.env.MINIO_ENDPOINT || "localhost",
|
||||
...(shouldOmitPort ? {} : { port }),
|
||||
useSSL,
|
||||
accessKey: process.env.MINIO_ACCESS_KEY || '',
|
||||
secretKey: process.env.MINIO_SECRET_KEY || '',
|
||||
accessKey: process.env.MINIO_ACCESS_KEY || "",
|
||||
secretKey: process.env.MINIO_SECRET_KEY || "",
|
||||
pathStyle: true, // 使用路径风格,对反向代理更友好
|
||||
});
|
||||
|
||||
const BUCKET_NAME = process.env.MINIO_BUCKET_NAME || 'home-page';
|
||||
const BUCKET_NAME = process.env.MINIO_BUCKET_NAME || "home-page";
|
||||
|
||||
/**
|
||||
* 初始化 MinIO Bucket(确保 bucket 存在)
|
||||
@@ -25,41 +25,39 @@ export async function initBucket(): Promise<void> {
|
||||
try {
|
||||
const exists = await minioClient.bucketExists(BUCKET_NAME);
|
||||
if (!exists) {
|
||||
await minioClient.makeBucket(BUCKET_NAME, 'us-east-1');
|
||||
await minioClient.makeBucket(BUCKET_NAME, "us-east-1");
|
||||
console.log(`Bucket ${BUCKET_NAME} created successfully`);
|
||||
}
|
||||
// 设置公共读取策略(可选)
|
||||
const policy = {
|
||||
Version: '2012-10-17',
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: 'Allow',
|
||||
Principal: { AWS: ['*'] },
|
||||
Action: ['s3:GetObject'],
|
||||
Effect: "Allow",
|
||||
Principal: { AWS: ["*"] },
|
||||
Action: ["s3:GetObject"],
|
||||
Resource: [`arn:aws:s3:::${BUCKET_NAME}/*`],
|
||||
},
|
||||
],
|
||||
};
|
||||
await minioClient.setBucketPolicy(BUCKET_NAME, JSON.stringify(policy));
|
||||
} catch (error) {
|
||||
console.error('Error initializing bucket:', error);
|
||||
console.error("Error initializing bucket:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件到 MinIO
|
||||
* @param file - File 对象或 Buffer
|
||||
* @param path - 文件存储路径(如: 'avatars/user123.jpg' 或 'posts/2024/image.png')
|
||||
* @param metadata - 可选的元数据
|
||||
* @returns 文件 path = args.path
|
||||
* @returns 文件 path = args.path
|
||||
*/
|
||||
export async function uploadFile(
|
||||
file: File | Buffer,
|
||||
path: string,
|
||||
metadata?: Record<string, string>
|
||||
metadata?: Record<string, string>,
|
||||
): Promise<string> {
|
||||
try {
|
||||
await initBucket();
|
||||
@@ -69,27 +67,32 @@ export async function uploadFile(
|
||||
|
||||
if (file instanceof File) {
|
||||
buffer = Buffer.from(await file.arrayBuffer());
|
||||
contentType = file.type || 'application/octet-stream';
|
||||
contentType = file.type || "application/octet-stream";
|
||||
} else {
|
||||
buffer = file;
|
||||
contentType = metadata?.['Content-Type'] || 'application/octet-stream';
|
||||
contentType = metadata?.["Content-Type"] || "application/octet-stream";
|
||||
}
|
||||
|
||||
const metaData = {
|
||||
'Content-Type': contentType,
|
||||
"Content-Type": contentType,
|
||||
...metadata,
|
||||
};
|
||||
|
||||
await minioClient.putObject(BUCKET_NAME, path, buffer, buffer.length, metaData);
|
||||
await minioClient.putObject(
|
||||
BUCKET_NAME,
|
||||
path,
|
||||
buffer,
|
||||
buffer.length,
|
||||
metaData,
|
||||
);
|
||||
|
||||
return path;
|
||||
} catch (error) {
|
||||
console.error('Error uploading file:', error);
|
||||
console.error("Error uploading file:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取文件的公共访问URL
|
||||
* @param path - 文件路径
|
||||
@@ -112,12 +115,12 @@ export async function downloadFile(path: string): Promise<Buffer> {
|
||||
const stream = await minioClient.getObject(BUCKET_NAME, path);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('data', (chunk) => chunks.push(chunk));
|
||||
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
stream.on('error', reject);
|
||||
stream.on("data", (chunk) => chunks.push(chunk));
|
||||
stream.on("end", () => resolve(Buffer.concat(chunks)));
|
||||
stream.on("error", reject);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
console.error("Error downloading file:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -127,11 +130,13 @@ export async function downloadFile(path: string): Promise<Buffer> {
|
||||
* @param path - 文件路径
|
||||
* @returns 文件流
|
||||
*/
|
||||
export async function getFileStream(path: string): Promise<NodeJS.ReadableStream> {
|
||||
export async function getFileStream(
|
||||
path: string,
|
||||
): Promise<NodeJS.ReadableStream> {
|
||||
try {
|
||||
return await minioClient.getObject(BUCKET_NAME, path);
|
||||
} catch (error) {
|
||||
console.error('Error getting file stream:', error);
|
||||
console.error("Error getting file stream:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -144,7 +149,7 @@ export async function deleteFile(path: string): Promise<void> {
|
||||
try {
|
||||
await minioClient.removeObject(BUCKET_NAME, path);
|
||||
} catch (error) {
|
||||
console.error('Error deleting file:', error);
|
||||
console.error("Error deleting file:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -157,7 +162,7 @@ export async function deleteFiles(paths: string[]): Promise<void> {
|
||||
try {
|
||||
await minioClient.removeObjects(BUCKET_NAME, paths);
|
||||
} catch (error) {
|
||||
console.error('Error deleting files:', error);
|
||||
console.error("Error deleting files:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -185,7 +190,7 @@ export async function getFileInfo(path: string): Promise<Minio.BucketItemStat> {
|
||||
try {
|
||||
return await minioClient.statObject(BUCKET_NAME, path);
|
||||
} catch (error) {
|
||||
console.error('Error getting file info:', error);
|
||||
console.error("Error getting file info:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -197,24 +202,27 @@ export async function getFileInfo(path: string): Promise<Minio.BucketItemStat> {
|
||||
* @returns 文件列表
|
||||
*/
|
||||
export async function listFiles(
|
||||
prefix: string = '',
|
||||
recursive: boolean = false
|
||||
prefix: string = "",
|
||||
recursive: boolean = false,
|
||||
): Promise<(Minio.BucketItem & { endpoint: string })[]> {
|
||||
try {
|
||||
const files: (Minio.BucketItem & { endpoint: string })[] = [];
|
||||
const stream = minioClient.listObjects(BUCKET_NAME, prefix, recursive);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('data', (obj) => {
|
||||
stream.on("data", (obj) => {
|
||||
if (obj.name) {
|
||||
files.push({ endpoint: `${process.env.MINIO_PUBLIC_DOMAIN}/${BUCKET_NAME}`, ...obj, } as Minio.BucketItem & { endpoint: string });
|
||||
files.push({
|
||||
endpoint: `${process.env.MINIO_PUBLIC_DOMAIN}/${BUCKET_NAME}`,
|
||||
...obj,
|
||||
} as Minio.BucketItem & { endpoint: string });
|
||||
}
|
||||
});
|
||||
stream.on('end', () => resolve(files));
|
||||
stream.on('error', reject);
|
||||
stream.on("end", () => resolve(files));
|
||||
stream.on("error", reject);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error listing files:', error);
|
||||
console.error("Error listing files:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -224,17 +232,20 @@ export async function listFiles(
|
||||
* @param sourcePath - 源文件路径
|
||||
* @param destPath - 目标文件路径
|
||||
*/
|
||||
export async function copyFile(sourcePath: string, destPath: string): Promise<void> {
|
||||
export async function copyFile(
|
||||
sourcePath: string,
|
||||
destPath: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const conds = new Minio.CopyConditions();
|
||||
await minioClient.copyObject(
|
||||
BUCKET_NAME,
|
||||
destPath,
|
||||
`/${BUCKET_NAME}/${sourcePath}`,
|
||||
conds
|
||||
conds,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error copying file:', error);
|
||||
console.error("Error copying file:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -245,15 +256,20 @@ export async function copyFile(sourcePath: string, destPath: string): Promise<vo
|
||||
* @param prefix - 路径前缀(如: 'avatars/' 或 'posts/2024/')
|
||||
* @returns 带路径的唯一文件名
|
||||
*/
|
||||
export function generateUniqueFileName(originalName: string, prefix: string = ''): string {
|
||||
export function generateUniqueFileName(
|
||||
originalName: string,
|
||||
prefix: string = "",
|
||||
): string {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.random().toString(36).substring(2, 8);
|
||||
const ext = originalName.split('.').pop();
|
||||
const nameWithoutExt = originalName.replace(`.${ext}`, '').replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const ext = originalName.split(".").pop();
|
||||
const nameWithoutExt = originalName
|
||||
.replace(`.${ext}`, "")
|
||||
.replace(/[^a-zA-Z0-9]/g, "_");
|
||||
|
||||
const fileName = `${nameWithoutExt}_${timestamp}_${random}.${ext}`;
|
||||
|
||||
return prefix ? `${prefix.replace(/\/$/, '')}/${fileName}` : fileName;
|
||||
return prefix ? `${prefix.replace(/\/$/, "")}/${fileName}` : fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,7 +278,7 @@ export function generateUniqueFileName(originalName: string, prefix: string = ''
|
||||
* @returns 扩展名(不含点)
|
||||
*/
|
||||
export function getFileExtension(filename: string): string {
|
||||
return filename.split('.').pop() || '';
|
||||
return filename.split(".").pop() || "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,9 +287,12 @@ export function getFileExtension(filename: string): string {
|
||||
* @param allowedTypes - 允许的扩展名数组(如: ['jpg', 'png', 'gif'])
|
||||
* @returns 是否为允许的类型
|
||||
*/
|
||||
export function validateFileType(filename: string, allowedTypes: string[]): boolean {
|
||||
export function validateFileType(
|
||||
filename: string,
|
||||
allowedTypes: string[],
|
||||
): boolean {
|
||||
const ext = getFileExtension(filename).toLowerCase();
|
||||
return allowedTypes.map(t => t.toLowerCase()).includes(ext);
|
||||
return allowedTypes.map((t) => t.toLowerCase()).includes(ext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,10 +311,10 @@ export function validateFileSize(size: number, maxSize: number): boolean {
|
||||
* @returns 格式化后的文件大小
|
||||
*/
|
||||
export function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
if (bytes === 0) return "0 B";
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined
|
||||
}
|
||||
prisma: PrismaClient | undefined;
|
||||
};
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
||||
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||
|
||||
Reference in New Issue
Block a user