mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
feat: add channel categories and improved creator detection
- Add category field to TelegramChannel (filterable tag like STL, PDF, D&D) - Category column in channels table with edit via dropdown menu - Improved creator extraction: filename patterns + channel title fallback - extractCreatorFromChannelTitle strips [Completed], (Paid), emoji, etc. - Fix ArchiveType in PackageListItem and PackageRow for new types - Add Prisma migration for category column
This commit is contained in:
@@ -12,7 +12,7 @@ export interface PackageRow {
|
||||
fileName: string;
|
||||
fileSize: string;
|
||||
contentHash: string;
|
||||
archiveType: "ZIP" | "RAR";
|
||||
archiveType: "ZIP" | "RAR" | "SEVEN_Z" | "DOCUMENT";
|
||||
fileCount: number;
|
||||
isMultipart: boolean;
|
||||
hasPreview: boolean;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ArrowDownToLine,
|
||||
ArrowUpFromLine,
|
||||
RefreshCcw,
|
||||
Tag,
|
||||
} from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -25,6 +26,7 @@ interface ChannelColumnsProps {
|
||||
onDelete: (id: string) => void;
|
||||
onSetType: (id: string, type: "SOURCE" | "DESTINATION") => void;
|
||||
onRescan: (id: string) => void;
|
||||
onSetCategory: (id: string, category: string | null) => void;
|
||||
}
|
||||
|
||||
export function getChannelColumns({
|
||||
@@ -32,6 +34,7 @@ export function getChannelColumns({
|
||||
onDelete,
|
||||
onSetType,
|
||||
onRescan,
|
||||
onSetCategory,
|
||||
}: ChannelColumnsProps): ColumnDef<ChannelRow, unknown>[] {
|
||||
return [
|
||||
{
|
||||
@@ -63,6 +66,18 @@ export function getChannelColumns({
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "category",
|
||||
header: "Category",
|
||||
cell: ({ row }) => {
|
||||
const category = row.original.category;
|
||||
return category ? (
|
||||
<Badge variant="outline">{category}</Badge>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">—</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "isActive",
|
||||
header: "Status",
|
||||
@@ -132,6 +147,15 @@ export function getChannelColumns({
|
||||
Rescan Channel
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
const cat = prompt("Enter category (e.g. STL, PDF, D&D, Cosplay):", row.original.category ?? "");
|
||||
if (cat !== null) onSetCategory(row.original.id, cat || null);
|
||||
}}
|
||||
>
|
||||
<Tag className="mr-2 h-3.5 w-3.5" />
|
||||
Set Category
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onToggleActive(row.original.id)}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
deleteChannel,
|
||||
toggleChannelActive,
|
||||
setChannelType,
|
||||
setChannelCategory,
|
||||
rescanChannel,
|
||||
} from "../actions";
|
||||
import { DataTable } from "@/components/shared/data-table";
|
||||
@@ -50,6 +51,13 @@ export function ChannelsTab({ channels, globalDestination, accounts }: ChannelsT
|
||||
});
|
||||
},
|
||||
onRescan: (id) => setRescanId(id),
|
||||
onSetCategory: (id, category) => {
|
||||
startTransition(async () => {
|
||||
const result = await setChannelCategory(id, category);
|
||||
if (result.success) toast.success(category ? `Category set to "${category}"` : "Category removed");
|
||||
else toast.error(result.error);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { table } = useDataTable({
|
||||
|
||||
@@ -259,6 +259,25 @@ export async function deleteChannel(id: string): Promise<ActionResult> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function setChannelCategory(
|
||||
id: string,
|
||||
category: string | null
|
||||
): Promise<ActionResult> {
|
||||
const admin = await requireAdmin();
|
||||
if (!admin.success) return admin;
|
||||
|
||||
try {
|
||||
await prisma.telegramChannel.update({
|
||||
where: { id },
|
||||
data: { category: category?.trim() || null },
|
||||
});
|
||||
revalidatePath("/telegram");
|
||||
return { success: true, data: undefined };
|
||||
} catch {
|
||||
return { success: false, error: "Failed to update category" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function setChannelType(
|
||||
id: string,
|
||||
type: "SOURCE" | "DESTINATION"
|
||||
|
||||
@@ -42,6 +42,7 @@ export async function listChannels() {
|
||||
title: c.title,
|
||||
type: c.type,
|
||||
isActive: c.isActive,
|
||||
category: c.category,
|
||||
createdAt: c.createdAt.toISOString(),
|
||||
accountCount: c._count.accountMaps,
|
||||
packageCount: c._count.packages,
|
||||
|
||||
@@ -3,7 +3,7 @@ export interface PackageListItem {
|
||||
fileName: string;
|
||||
fileSize: string; // BigInt serialized as string
|
||||
contentHash: string;
|
||||
archiveType: "ZIP" | "RAR";
|
||||
archiveType: "ZIP" | "RAR" | "SEVEN_Z" | "DOCUMENT";
|
||||
fileCount: number;
|
||||
isMultipart: boolean;
|
||||
hasPreview: boolean;
|
||||
|
||||
Reference in New Issue
Block a user