398 lines
9.3 KiB
TypeScript
398 lines
9.3 KiB
TypeScript
import { Prisma, UserFile } from "@prisma/client";
|
|
|
|
import { prisma } from "../db";
|
|
import { bytesToStorageValue, storageValueToBytes } from "../utils";
|
|
|
|
export interface UserFileData extends UserFile {
|
|
user: {
|
|
name: string;
|
|
email: string;
|
|
};
|
|
}
|
|
|
|
export interface CreateUserFileInput {
|
|
userId: string;
|
|
name: string;
|
|
originalName?: string;
|
|
mimeType: string;
|
|
size: number;
|
|
path: string;
|
|
etag?: string;
|
|
storageClass?: string;
|
|
channel: string;
|
|
platform: string;
|
|
providerName: string;
|
|
bucket: string;
|
|
shortUrlId?: string;
|
|
lastModified: Date;
|
|
}
|
|
|
|
export interface UpdateUserFileInput {
|
|
name?: string;
|
|
originalName?: string;
|
|
mimeType?: string;
|
|
size?: number;
|
|
path?: string;
|
|
etag?: string;
|
|
storageClass?: string;
|
|
channel?: string;
|
|
platform?: string;
|
|
providerName?: string;
|
|
bucket?: string;
|
|
shortUrlId?: string;
|
|
status?: number;
|
|
lastModified?: Date;
|
|
}
|
|
|
|
export interface QueryUserFileOptions {
|
|
bucket?: string;
|
|
userId?: string;
|
|
providerName?: string;
|
|
status?: number;
|
|
channel?: string;
|
|
platform?: string;
|
|
shortUrlId?: string;
|
|
name?: string;
|
|
size?: number;
|
|
mimeType?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
orderBy?: "createdAt" | "lastModified" | "size";
|
|
order?: "asc" | "desc";
|
|
}
|
|
|
|
// 创建文件记录
|
|
export async function createUserFile(data: CreateUserFileInput) {
|
|
try {
|
|
const userFile = await prisma.userFile.create({
|
|
data: {
|
|
...data,
|
|
updatedAt: new Date(),
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
return { success: true, data: userFile };
|
|
} catch (error) {
|
|
console.error("Failed to create file record:", error);
|
|
return { success: false, error: "Failed to create file record" };
|
|
}
|
|
}
|
|
|
|
// 根据ID查询文件记录
|
|
export async function getUserFileById(id: string) {
|
|
try {
|
|
const userFile = await prisma.userFile.findUnique({
|
|
where: { id },
|
|
include: {
|
|
user: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
return { success: true, data: userFile };
|
|
} catch (error) {
|
|
console.error("Failed to query file record:", error);
|
|
return { success: false, error: "Failed to query file record" };
|
|
}
|
|
}
|
|
|
|
// 条件查询文件记录
|
|
export async function getUserFiles(options: QueryUserFileOptions = {}) {
|
|
try {
|
|
const {
|
|
bucket,
|
|
userId,
|
|
providerName,
|
|
status,
|
|
channel,
|
|
platform,
|
|
shortUrlId,
|
|
name,
|
|
size,
|
|
mimeType,
|
|
page = 1,
|
|
limit = 20,
|
|
orderBy = "createdAt",
|
|
order = "desc",
|
|
} = options;
|
|
|
|
const where: Prisma.UserFileWhereInput = {
|
|
bucket,
|
|
...(status !== undefined && { status }),
|
|
...(userId && { userId }),
|
|
...(providerName && { providerName }),
|
|
...(channel && { channel }),
|
|
...(platform && { platform }),
|
|
...(shortUrlId && { shortUrlId }),
|
|
...(name && { name: { contains: name, mode: "insensitive" } }),
|
|
...(size && { size: { gte: bytesToStorageValue(size) } }),
|
|
...(mimeType && {
|
|
mimeType: { contains: mimeType, mode: "insensitive" },
|
|
}),
|
|
};
|
|
|
|
const [files, total, totalSize] = await Promise.all([
|
|
prisma.userFile.findMany({
|
|
where,
|
|
include: {
|
|
user: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { [orderBy]: order },
|
|
skip: (page - 1) * limit,
|
|
take: limit,
|
|
}),
|
|
prisma.userFile.count({ where }),
|
|
prisma.userFile.aggregate({
|
|
where: {
|
|
// bucket,
|
|
// providerName,
|
|
status: 1,
|
|
...(userId && { userId }),
|
|
},
|
|
_sum: { size: true },
|
|
}),
|
|
]);
|
|
|
|
return {
|
|
total,
|
|
totalSize: storageValueToBytes(totalSize._sum.size || 0),
|
|
list: files,
|
|
};
|
|
} catch (error) {
|
|
console.error("[GetUserFiles Error]", error);
|
|
return { success: false, error: "[GetUserFiles Error]" };
|
|
}
|
|
}
|
|
|
|
// 更新文件记录
|
|
export async function updateUserFile(id: string, data: UpdateUserFileInput) {
|
|
try {
|
|
const userFile = await prisma.userFile.update({
|
|
where: { id },
|
|
data: {
|
|
...data,
|
|
updatedAt: new Date(),
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
return { success: true, data: userFile };
|
|
} catch (error) {
|
|
console.error("Failed to update file record:", error);
|
|
return { success: false, error: "Failed to update file record" };
|
|
}
|
|
}
|
|
|
|
// 软删除文件记录
|
|
export async function softDeleteUserFile(id: string) {
|
|
try {
|
|
const userFile = await prisma.userFile.update({
|
|
where: { id },
|
|
data: {
|
|
status: 0,
|
|
updatedAt: new Date(),
|
|
},
|
|
});
|
|
return { success: true, data: userFile };
|
|
} catch (error) {
|
|
console.error("Delete file record failed:", error);
|
|
return { success: false, error: "Delete file record failed" };
|
|
}
|
|
}
|
|
|
|
// 批量软删除
|
|
export async function softDeleteUserFiles(ids: string[]) {
|
|
try {
|
|
const result = await prisma.userFile.updateMany({
|
|
where: {
|
|
id: { in: ids },
|
|
},
|
|
data: {
|
|
status: 0,
|
|
updatedAt: new Date(),
|
|
},
|
|
});
|
|
return { success: true, data: result };
|
|
} catch (error) {
|
|
console.error("Delete file records failed:", error);
|
|
return { success: false, error: "Delete file records failed" };
|
|
}
|
|
}
|
|
|
|
// 物理删除文件记录
|
|
export async function deleteUserFile(id: string) {
|
|
try {
|
|
const userFile = await prisma.userFile.delete({
|
|
where: { id },
|
|
});
|
|
return { success: true, data: userFile };
|
|
} catch (error) {
|
|
console.error("Delete file record failed:", error);
|
|
return { success: false, error: "Delete file record failed" };
|
|
}
|
|
}
|
|
|
|
// 获取用户文件统计
|
|
export async function getUserFileStats(userId: string) {
|
|
try {
|
|
const [totalFiles, totalSize, filesByProvider] = await Promise.all([
|
|
prisma.userFile.count({
|
|
where: { userId, status: 1 },
|
|
}),
|
|
prisma.userFile.aggregate({
|
|
where: { userId, status: 1 },
|
|
_sum: { size: true },
|
|
}),
|
|
prisma.userFile.groupBy({
|
|
by: ["providerName"],
|
|
where: { userId, status: 1 },
|
|
_count: { id: true },
|
|
_sum: { size: true },
|
|
}),
|
|
]);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
totalFiles,
|
|
totalSize: storageValueToBytes(totalSize._sum.size || 0),
|
|
filesByProvider,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error("Failed to get file statistics:", error);
|
|
return { success: false, error: "Failed to get file statistics" };
|
|
}
|
|
}
|
|
|
|
// 根据路径查找文件
|
|
export async function getUserFileByPath(path: string, providerName?: string) {
|
|
try {
|
|
const where: Prisma.UserFileWhereInput = {
|
|
path,
|
|
status: 1,
|
|
...(providerName && { providerName }),
|
|
};
|
|
|
|
const userFile = await prisma.userFile.findFirst({
|
|
where,
|
|
include: {
|
|
user: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return { success: true, data: userFile };
|
|
} catch (error) {
|
|
console.error("Failed to query file record:", error);
|
|
return { success: false, error: "Failed to query file record" };
|
|
}
|
|
}
|
|
|
|
// 根据短链接ID查询文件
|
|
export async function getUserFileByShortUrlId(shortUrlId: string) {
|
|
try {
|
|
const userFile = await prisma.userFile.findFirst({
|
|
where: {
|
|
shortUrlId,
|
|
status: 1,
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
return { success: true, data: userFile };
|
|
} catch (error) {
|
|
console.error("Failed to query file record:", error);
|
|
return { success: false, error: "Failed to query file record" };
|
|
}
|
|
}
|
|
|
|
// 清理过期文件记录
|
|
export async function cleanupExpiredFiles(days: number = 30) {
|
|
try {
|
|
const expiredDate = new Date();
|
|
expiredDate.setDate(expiredDate.getDate() - days);
|
|
|
|
const result = await prisma.userFile.deleteMany({
|
|
where: {
|
|
status: 0,
|
|
updatedAt: {
|
|
lt: expiredDate,
|
|
},
|
|
},
|
|
});
|
|
|
|
return { success: true, data: result };
|
|
} catch (error) {
|
|
console.error("Failed to clean up expired files:", error);
|
|
return { success: false, error: "Failed to clean up expired files" };
|
|
}
|
|
}
|
|
|
|
// 获取特定存储桶的使用量统计
|
|
export async function getBucketStorageUsage(
|
|
bucket: string,
|
|
providerName: string,
|
|
): Promise<
|
|
| { success: true; data: { totalSize: number; totalFiles: number } }
|
|
| { success: false; error: string }
|
|
> {
|
|
try {
|
|
const result = await prisma.userFile.aggregate({
|
|
where: {
|
|
bucket,
|
|
providerName,
|
|
status: 1,
|
|
},
|
|
_sum: {
|
|
size: true,
|
|
},
|
|
_count: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
totalSize: storageValueToBytes(result._sum.size || 0),
|
|
totalFiles: result._count.id || 0,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error("Failed to get bucket storage usage:", error);
|
|
return { success: false, error: "Failed to get bucket storage usage" };
|
|
}
|
|
}
|