mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-06-13 12:41:16 +00:00
feat: grouping phase 1 — schema, ungrouped tab, time-window grouping, hash verification
Schema: - Add GroupingSource enum (ALBUM, MANUAL, AUTO_TIME, AUTO_PATTERN, etc.) - Add groupingSource field to PackageGroup with backfill - Add SystemNotification model for persistent alerts - Add NotificationType and NotificationSeverity enums Ungrouped staging tab: - Add listUngroupedPackages/countUngroupedPackages queries - Add "Ungrouped" tab to STL page showing packages without a group Time-window auto-grouping: - After album grouping, cluster ungrouped packages within configurable time window (default 5 min, AUTO_GROUP_TIME_WINDOW_MINUTES env var) - Groups named from common filename prefix - Groups created with groupingSource=AUTO_TIME Hash verification after split: - Re-hash split parts and compare to original contentHash - Log error and create SystemNotification on mismatch - Prevents silently corrupted split uploads Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -571,6 +571,72 @@ export async function countSkippedPackages(): Promise<number> {
|
||||
return prisma.skippedPackage.count();
|
||||
}
|
||||
|
||||
export async function listUngroupedPackages(options: {
|
||||
page: number;
|
||||
limit: number;
|
||||
}) {
|
||||
const { page, limit } = options;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where = { packageGroupId: null, destMessageId: { not: null } };
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
prisma.package.findMany({
|
||||
where,
|
||||
orderBy: { indexedAt: "desc" },
|
||||
skip,
|
||||
take: limit,
|
||||
select: {
|
||||
id: true,
|
||||
fileName: true,
|
||||
fileSize: true,
|
||||
archiveType: true,
|
||||
creator: true,
|
||||
fileCount: true,
|
||||
isMultipart: true,
|
||||
partCount: true,
|
||||
tags: true,
|
||||
indexedAt: true,
|
||||
previewData: true,
|
||||
sourceChannel: { select: { id: true, title: true } },
|
||||
},
|
||||
}),
|
||||
prisma.package.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
items: items.map((p) => ({
|
||||
id: p.id,
|
||||
fileName: p.fileName,
|
||||
fileSize: p.fileSize.toString(),
|
||||
contentHash: "",
|
||||
archiveType: p.archiveType,
|
||||
creator: p.creator,
|
||||
fileCount: p.fileCount,
|
||||
isMultipart: p.isMultipart,
|
||||
partCount: p.partCount,
|
||||
tags: p.tags,
|
||||
indexedAt: p.indexedAt.toISOString(),
|
||||
hasPreview: !!p.previewData,
|
||||
sourceChannel: p.sourceChannel,
|
||||
matchedFileCount: 0,
|
||||
matchedByContent: false,
|
||||
})),
|
||||
pagination: {
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
page,
|
||||
limit,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function countUngroupedPackages(): Promise<number> {
|
||||
return prisma.package.count({
|
||||
where: { packageGroupId: null, destMessageId: { not: null } },
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPackageGroup(groupId: string) {
|
||||
return prisma.packageGroup.findUnique({
|
||||
where: { id: groupId },
|
||||
|
||||
Reference in New Issue
Block a user