mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
feat: fix channel scanning bugs, add package tags, and kickstarters tab
Bug fixes: - Fix channels not being scanned by paginating TDLib getChats (was only loading first batch, additional channels were unknown to TDLib) - Add per-channel getChat pre-load as safety net before scanning - Fix preview pictures not loading by checking previewData instead of previewMsgId for hasPreview flag - Prevent previewMsgId from being set when preview download fails Package Tags: - Add tags Text[] column to Package with migration backfilling from channel categories - Worker auto-inherits source channel category as initial tag - Tag filter dropdown and Tags column in STL Files table - Server actions for individual and bulk tag editing Kickstarters Tab: - New KickstarterHost, Kickstarter, and KickstarterPackage models - Full CRUD with delivery status, payment status, host management - Package linking (many-to-many with existing packages) - Sidebar entry with Gift icon - Table with search, filters, modal forms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ export const NAV_ITEMS = [
|
||||
{ label: "Paints", href: "/paints", icon: "Paintbrush", adminOnly: false },
|
||||
{ label: "Supplies", href: "/supplies", icon: "Gem", adminOnly: false },
|
||||
{ label: "STL Files", href: "/stls", icon: "FileBox", adminOnly: false },
|
||||
{ label: "Kickstarters", href: "/kickstarters", icon: "Gift", adminOnly: false },
|
||||
{ label: "Telegram", href: "/telegram", icon: "Send", adminOnly: true },
|
||||
{ label: "Invites", href: "/invites", icon: "UserPlus", adminOnly: true },
|
||||
{ label: "Usage", href: "/usage", icon: "ClipboardList", adminOnly: false },
|
||||
|
||||
@@ -11,12 +11,14 @@ export async function listPackages(options: {
|
||||
limit: number;
|
||||
channelId?: string;
|
||||
creator?: string;
|
||||
tag?: string;
|
||||
sortBy: "indexedAt" | "fileName" | "fileSize";
|
||||
order: "asc" | "desc";
|
||||
}) {
|
||||
const where: Record<string, unknown> = {};
|
||||
if (options.channelId) where.sourceChannelId = options.channelId;
|
||||
if (options.creator) where.creator = options.creator;
|
||||
if (options.tag) where.tags = { has: options.tag };
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
prisma.package.findMany({
|
||||
@@ -34,7 +36,8 @@ export async function listPackages(options: {
|
||||
isMultipart: true,
|
||||
indexedAt: true,
|
||||
creator: true,
|
||||
previewMsgId: true, // cheap null check — avoids loading blob
|
||||
tags: true,
|
||||
previewData: true, // check actual image data, not previewMsgId proxy
|
||||
sourceChannel: { select: { id: true, title: true } },
|
||||
},
|
||||
}),
|
||||
@@ -49,8 +52,9 @@ export async function listPackages(options: {
|
||||
archiveType: pkg.archiveType,
|
||||
fileCount: pkg.fileCount,
|
||||
isMultipart: pkg.isMultipart,
|
||||
hasPreview: pkg.previewMsgId !== null,
|
||||
hasPreview: pkg.previewData !== null,
|
||||
creator: pkg.creator,
|
||||
tags: pkg.tags,
|
||||
indexedAt: pkg.indexedAt.toISOString(),
|
||||
sourceChannel: pkg.sourceChannel,
|
||||
}));
|
||||
@@ -96,8 +100,9 @@ export async function getPackageById(
|
||||
archiveType: pkg.archiveType,
|
||||
fileCount: pkg.fileCount,
|
||||
isMultipart: pkg.isMultipart,
|
||||
hasPreview: pkg.previewMsgId !== null,
|
||||
hasPreview: pkg.previewData !== null,
|
||||
creator: pkg.creator,
|
||||
tags: pkg.tags,
|
||||
partCount: pkg.partCount,
|
||||
indexedAt: pkg.indexedAt.toISOString(),
|
||||
sourceChannel: pkg.sourceChannel,
|
||||
@@ -208,7 +213,8 @@ export async function searchPackages(options: {
|
||||
isMultipart: true,
|
||||
indexedAt: true,
|
||||
creator: true,
|
||||
previewMsgId: true,
|
||||
tags: true,
|
||||
previewData: true,
|
||||
sourceChannel: { select: { id: true, title: true } },
|
||||
},
|
||||
}),
|
||||
@@ -223,8 +229,9 @@ export async function searchPackages(options: {
|
||||
archiveType: pkg.archiveType,
|
||||
fileCount: pkg.fileCount,
|
||||
isMultipart: pkg.isMultipart,
|
||||
hasPreview: pkg.previewMsgId !== null,
|
||||
hasPreview: pkg.previewData !== null,
|
||||
creator: pkg.creator,
|
||||
tags: pkg.tags,
|
||||
indexedAt: pkg.indexedAt.toISOString(),
|
||||
sourceChannel: pkg.sourceChannel,
|
||||
}));
|
||||
@@ -249,6 +256,16 @@ export async function searchPackages(options: {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all distinct tags across all packages (for filter dropdowns).
|
||||
*/
|
||||
export async function getAllPackageTags(): Promise<string[]> {
|
||||
const result = await prisma.$queryRaw<{ tag: string }[]>`
|
||||
SELECT DISTINCT unnest(tags) AS tag FROM packages ORDER BY tag
|
||||
`;
|
||||
return result.map((r) => r.tag);
|
||||
}
|
||||
|
||||
export async function getIngestionStatus(): Promise<IngestionAccountStatus[]> {
|
||||
const accounts = await prisma.telegramAccount.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface PackageListItem {
|
||||
isMultipart: boolean;
|
||||
hasPreview: boolean;
|
||||
creator: string | null;
|
||||
tags: string[];
|
||||
indexedAt: string;
|
||||
sourceChannel: {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user