From 50e89719bbaf768a86f1c0f868e5d363a7b89ae8 Mon Sep 17 00:00:00 2001 From: xCyanGrizzly Date: Sat, 4 Jul 2026 09:20:36 +0200 Subject: [PATCH 1/3] feat(stls): add sendAllFromCreatorAction server action --- src/app/(app)/stls/actions.ts | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/app/(app)/stls/actions.ts b/src/app/(app)/stls/actions.ts index d58a074..5e7ba5a 100644 --- a/src/app/(app)/stls/actions.ts +++ b/src/app/(app)/stls/actions.ts @@ -589,3 +589,82 @@ export async function sendAllInGroupAction( return { success: false, error: "Failed to send group packages" }; } } + +export async function sendAllFromCreatorAction( + creatorName: string +): Promise> { + const session = await auth(); + if (!session?.user?.id) return { success: false, error: "Unauthorized" }; + + const creator = creatorName.trim(); + if (!creator) { + return { success: false, error: "No creator specified" }; + } + + try { + const telegramLink = await prisma.telegramLink.findUnique({ + where: { userId: session.user.id }, + }); + + if (!telegramLink) { + return { success: false, error: "No linked Telegram account. Link one in Settings." }; + } + + const sendablePackages = await prisma.package.findMany({ + where: { + creator, + destChannelId: { not: null }, + destMessageId: { not: null }, + }, + select: { id: true }, + }); + + if (sendablePackages.length === 0) { + return { success: false, error: "No uploaded packages found for this creator" }; + } + + let queued = 0; + let skipped = 0; + for (const pkg of sendablePackages) { + // Only create if no existing PENDING/SENDING request for this package+link combo + const existing = await prisma.botSendRequest.findFirst({ + where: { + packageId: pkg.id, + telegramLinkId: telegramLink.id, + status: { in: ["PENDING", "SENDING"] }, + }, + }); + + if (existing) { + skipped++; + continue; + } + + const sendRequest = await prisma.botSendRequest.create({ + data: { + packageId: pkg.id, + telegramLinkId: telegramLink.id, + requestedByUserId: session.user.id, + status: "PENDING", + }, + }); + + // Notify the bot via pg_notify + try { + await prisma.$queryRawUnsafe( + `SELECT pg_notify('bot_send', $1)`, + sendRequest.id + ); + } catch { + // Best-effort — the bot also polls periodically + } + + queued++; + } + + revalidatePath("/stls"); + return { success: true, data: { queued, skipped } }; + } catch { + return { success: false, error: "Failed to send creator packages" }; + } +} From 20e60bc6af85112aa82cf2aab68eb28db29698aa Mon Sep 17 00:00:00 2001 From: xCyanGrizzly Date: Sat, 4 Jul 2026 09:26:36 +0200 Subject: [PATCH 2/3] feat(stls): add 'send all from creator' toolbar button --- src/app/(app)/stls/_components/stl-table.tsx | 32 +++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/app/(app)/stls/_components/stl-table.tsx b/src/app/(app)/stls/_components/stl-table.tsx index 9235e4d..fabc3ce 100644 --- a/src/app/(app)/stls/_components/stl-table.tsx +++ b/src/app/(app)/stls/_components/stl-table.tsx @@ -3,7 +3,7 @@ import { useState, useCallback, useTransition, useMemo, useRef } from "react"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { toast } from "sonner"; -import { Search, Layers, Upload } from "lucide-react"; +import { Search, Layers, Upload, Send } from "lucide-react"; import { UploadDialog } from "./upload-dialog"; import { useDataTable } from "@/hooks/use-data-table"; import { @@ -49,6 +49,7 @@ import { createGroupAction, removeFromGroupAction, sendAllInGroupAction, + sendAllFromCreatorAction, updateGroupPreviewAction, mergeGroupsAction, } from "../actions"; @@ -85,6 +86,7 @@ export function StlTable({ const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); + const activeCreator = searchParams.get("creator") ?? ""; const [searchValue, setSearchValue] = useState(searchParams.get("search") ?? ""); const [viewPkg, setViewPkg] = useState(null); @@ -277,6 +279,23 @@ export function StlTable({ [router] ); + const handleSendAllFromCreator = useCallback(() => { + if (!confirm(`Send all packages from "${activeCreator}" to your Telegram?`)) return; + startTransition(async () => { + const result = await sendAllFromCreatorAction(activeCreator); + if (result.success) { + const { queued, skipped } = result.data; + toast.success( + `Queued ${queued} package${queued === 1 ? "" : "s"} from ${activeCreator}` + + (skipped ? ` (${skipped} already queued)` : "") + ); + router.refresh(); + } else { + toast.error(result.error); + } + }); + }, [activeCreator, router]); + const handleRemoveFromGroup = useCallback( (packageId: string) => { startTransition(async () => { @@ -505,6 +524,17 @@ export function StlTable({ Upload Files + {activeCreator && ( + + )} {selectedPackages.size >= 2 && ( + + + + + + No creators found. + + { + onChange(""); + setOpen(false); + }} + > + + All Creators + + {creators.map((creator) => ( + { + onChange(creator); + setOpen(false); + }} + > + + {creator} + + ))} + + + + + + ); +} diff --git a/src/app/(app)/stls/_components/stl-table.tsx b/src/app/(app)/stls/_components/stl-table.tsx index fabc3ce..d914bb0 100644 --- a/src/app/(app)/stls/_components/stl-table.tsx +++ b/src/app/(app)/stls/_components/stl-table.tsx @@ -16,6 +16,7 @@ import { import { PackageFilesDrawer } from "./package-files-drawer"; import { IngestionStatus } from "./ingestion-status"; import { SkippedPackagesTab } from "./skipped-packages-tab"; +import { CreatorFilter } from "./creator-filter"; import { DataTable } from "@/components/shared/data-table"; import { DataTablePagination } from "@/components/shared/data-table-pagination"; import { DataTableViewOptions } from "@/components/shared/data-table-view-options"; @@ -60,6 +61,7 @@ interface StlTableProps { totalCount: number; ingestionStatus: IngestionAccountStatus[]; availableTags: string[]; + availableCreators: string[]; searchTerm: string; skippedData: SkippedRow[]; skippedPageCount: number; @@ -75,6 +77,7 @@ export function StlTable({ totalCount, ingestionStatus, availableTags, + availableCreators, searchTerm, skippedData, skippedPageCount, @@ -209,6 +212,20 @@ export function StlTable({ [router, pathname, searchParams] ); + const updateCreatorFilter = useCallback( + (value: string) => { + const params = new URLSearchParams(searchParams.toString()); + if (value) { + params.set("creator", value); + params.set("page", "1"); + } else { + params.delete("creator"); + } + router.push(`${pathname}?${params.toString()}`, { scroll: false }); + }, + [router, pathname, searchParams] + ); + const activeTab = searchParams.get("tab") ?? "packages"; const updateTab = useCallback( @@ -519,6 +536,13 @@ export function StlTable({ )} + {availableCreators.length > 0 && ( + + )}