new storage for files
This commit is contained in:
parent
f7003fc6b9
commit
6acfc08169
31 changed files with 1620 additions and 1958 deletions
|
|
@ -13,6 +13,7 @@ TODOS:
|
||||||
- Modale conferma mostrare scorciatoia a doc lavoro o simile per facilitare
|
- Modale conferma mostrare scorciatoia a doc lavoro o simile per facilitare
|
||||||
- Post modale conferma, mostrare se transitorio , modale con informativa tipo:" il saldo del servizio e il periodo di permanenza veranno adeguati alle condizioni stabilite insieme al locatore nel momento della trattativa e alla documentazione presentata come giustificazione di transitorietà" (modificato)
|
- Post modale conferma, mostrare se transitorio , modale con informativa tipo:" il saldo del servizio e il periodo di permanenza veranno adeguati alle condizioni stabilite insieme al locatore nel momento della trattativa e alla documentazione presentata come giustificazione di transitorietà" (modificato)
|
||||||
|
|
||||||
|
- border radius immagini carousel annuncio
|
||||||
|
|
||||||
NEW IDEA TODOS:
|
NEW IDEA TODOS:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import dynamic from "next/dynamic";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import type { TempTokensToken } from "~/schemas/public/TempTokens";
|
import type { FileMetadata } from "~/server/services/storage.service";
|
||||||
import { api } from "~/utils/api";
|
|
||||||
|
|
||||||
const PDFViewer = dynamic(
|
const PDFViewer = dynamic(
|
||||||
() => import("./pdf-viewer").then((mod) => mod.PDFViewer),
|
() => import("./pdf-viewer").then((mod) => mod.PDFViewer),
|
||||||
|
|
@ -17,11 +16,10 @@ export const AllegatoIframe = ({
|
||||||
allegato,
|
allegato,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
allegato: string;
|
allegato: FileMetadata;
|
||||||
}) => {
|
}) => {
|
||||||
const if_ref = useRef<HTMLIFrameElement>(null);
|
const if_ref = useRef<HTMLIFrameElement>(null);
|
||||||
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
|
|
||||||
const [token, setToken] = useState<TempTokensToken | null>(null);
|
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
const [isIOS, setIsIOS] = useState(false);
|
const [isIOS, setIsIOS] = useState(false);
|
||||||
const [isPDF, setIsPDF] = useState(false);
|
const [isPDF, setIsPDF] = useState(false);
|
||||||
|
|
@ -37,7 +35,7 @@ export const AllegatoIframe = ({
|
||||||
setIsIOS(/iphone|ipad|ipod/i.test(userAgent));
|
setIsIOS(/iphone|ipad|ipod/i.test(userAgent));
|
||||||
|
|
||||||
// Try to detect if file is PDF from extension
|
// Try to detect if file is PDF from extension
|
||||||
setIsPDF(allegato.toLowerCase().endsWith(".pdf"));
|
setIsPDF(allegato.mimeType === "application/pdf");
|
||||||
}, [allegato]);
|
}, [allegato]);
|
||||||
|
|
||||||
const handleIframeLoad = () => {
|
const handleIframeLoad = () => {
|
||||||
|
|
@ -52,17 +50,7 @@ export const AllegatoIframe = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const requestUrl = `/storage-api/get/${allegato.id}?mode=inline`;
|
||||||
async function set() {
|
|
||||||
const token = await getToken();
|
|
||||||
setToken(token);
|
|
||||||
}
|
|
||||||
set().catch((e) => console.error(e));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!token) return <LoadingPage />;
|
|
||||||
|
|
||||||
const requestUrl = `/go-api/storage/get/${allegato}?token=${String(token)}`;
|
|
||||||
// For PDF on iOS, provide download link instead
|
// For PDF on iOS, provide download link instead
|
||||||
if (isPDF && (isIOS || isMobile)) {
|
if (isPDF && (isIOS || isMobile)) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -36,14 +36,11 @@ import {
|
||||||
} from "~/components/ui/dropdown-menu";
|
} from "~/components/ui/dropdown-menu";
|
||||||
import { Separator } from "~/components/ui/separator";
|
import { Separator } from "~/components/ui/separator";
|
||||||
import { UploadModal } from "~/components/upload_modal";
|
import { UploadModal } from "~/components/upload_modal";
|
||||||
import { handleDownload } from "~/hooks/filesHooks";
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type {
|
|
||||||
Storageindex,
|
|
||||||
StorageindexId,
|
|
||||||
} from "~/schemas/public/Storageindex";
|
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
|
import type { FileMetadata } from "~/server/services/storage.service";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export const AllegatiComp = ({
|
export const AllegatiComp = ({
|
||||||
|
|
@ -55,11 +52,10 @@ export const AllegatiComp = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data: allegati, isLoading: isLoadingAllegati } =
|
const { data: allegati, isLoading: isLoadingAllegati } =
|
||||||
api.storage.getUserStorage.useQuery({ userId: userId });
|
api.storage.retrieveUserFileData.useQuery({ userId });
|
||||||
|
|
||||||
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
|
const { mutate: deleteFile } = api.storage.deleteUserStorageEntry.useMutation(
|
||||||
|
{
|
||||||
const { mutate: deleteFile } = api.storage.deleteUserStorage.useMutation({
|
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
const toastId = toast.loading(t.caricamento, {
|
const toastId = toast.loading(t.caricamento, {
|
||||||
icon: "🪛",
|
icon: "🪛",
|
||||||
|
|
@ -67,7 +63,7 @@ export const AllegatiComp = ({
|
||||||
return { toastId };
|
return { toastId };
|
||||||
},
|
},
|
||||||
onSuccess: async (_error, _variables, context) => {
|
onSuccess: async (_error, _variables, context) => {
|
||||||
await utils.storage.getUserStorage.invalidate({
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
});
|
});
|
||||||
toast.success(t.allegati.allegato_rimosso, {
|
toast.success(t.allegati.allegato_rimosso, {
|
||||||
|
|
@ -75,7 +71,8 @@ export const AllegatiComp = ({
|
||||||
id: context?.toastId,
|
id: context?.toastId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const { mutate: renameFile } = api.storage.renameFile.useMutation({
|
const { mutate: renameFile } = api.storage.renameFile.useMutation({
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
|
|
@ -86,7 +83,7 @@ export const AllegatiComp = ({
|
||||||
return { toastId };
|
return { toastId };
|
||||||
},
|
},
|
||||||
onSuccess: async (_error, _variables, context) => {
|
onSuccess: async (_error, _variables, context) => {
|
||||||
await utils.storage.getUserStorage.invalidate({
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
});
|
});
|
||||||
toast.success(t.successo, {
|
toast.success(t.successo, {
|
||||||
|
|
@ -96,7 +93,7 @@ export const AllegatiComp = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const [editData, setEditData] = useState<{
|
const [editData, setEditData] = useState<{
|
||||||
id: StorageindexId;
|
id: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [editOpen, setEditOpen] = useState(false);
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
|
|
@ -109,7 +106,7 @@ export const AllegatiComp = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renameFile({
|
renameFile({
|
||||||
fileId: formData.get("id") as StorageindexId,
|
fileId: formData.get("id") as string,
|
||||||
name: formData.get("filename") as string,
|
name: formData.get("filename") as string,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -128,13 +125,7 @@ export const AllegatiComp = ({
|
||||||
<h1 className="font-semibold text-xl">{t.allegati.da_info}</h1>
|
<h1 className="font-semibold text-xl">{t.allegati.da_info}</h1>
|
||||||
{from_admin && (
|
{from_admin && (
|
||||||
<FileList
|
<FileList
|
||||||
delete_onClick={(id) => deleteFile({ fileId: id, userId })}
|
delete_onClick={(id) => deleteFile({ storageId: id, userId })}
|
||||||
download_onClick={(file) =>
|
|
||||||
handleDownload({
|
|
||||||
file,
|
|
||||||
getToken: async () => await getToken(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
files={from_admin}
|
files={from_admin}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
selectHandler={(id, filename) => {
|
selectHandler={(id, filename) => {
|
||||||
|
|
@ -158,13 +149,7 @@ export const AllegatiComp = ({
|
||||||
|
|
||||||
{not_from_admin && (
|
{not_from_admin && (
|
||||||
<FileList
|
<FileList
|
||||||
delete_onClick={(id) => deleteFile({ fileId: id, userId })}
|
delete_onClick={(id) => deleteFile({ storageId: id, userId })}
|
||||||
download_onClick={(file) =>
|
|
||||||
handleDownload({
|
|
||||||
file,
|
|
||||||
getToken: async () => await getToken(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
files={not_from_admin}
|
files={not_from_admin}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
selectHandler={(id, filename) => {
|
selectHandler={(id, filename) => {
|
||||||
|
|
@ -184,45 +169,56 @@ export const AllegatiComp = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExtIcon = ({
|
export const ExtIcon = ({
|
||||||
ext,
|
mimeType,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
ext: string | null | undefined;
|
mimeType: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!ext) return <File className={cn("size-5 text-gray-500", className)} />;
|
const lower = mimeType?.toLowerCase() || "";
|
||||||
if (ext.includes("pdf")) {
|
|
||||||
|
if (!lower) return <File className={cn("size-5 text-gray-500", className)} />;
|
||||||
|
|
||||||
|
// PDF
|
||||||
|
if (lower.includes("pdf")) {
|
||||||
return <FileText className={cn("size-5 text-red-500", className)} />;
|
return <FileText className={cn("size-5 text-red-500", className)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Images
|
||||||
if (
|
if (
|
||||||
ext.includes("image") ||
|
lower.startsWith("image/") ||
|
||||||
["jpg", "jpeg", "png", "gif", "svg", "webp"].some((ext) =>
|
/\b(jpg|jpeg|png|gif|svg|webp)\b/.test(lower)
|
||||||
ext.includes(ext),
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return <ImageIcon className={cn("size-5 text-blue-500", className)} />;
|
return <ImageIcon className={cn("size-5 text-blue-500", className)} />;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
["html", "css", "js", "jsx", "ts", "tsx", "json", "xml"].some((ext) =>
|
// Code files
|
||||||
ext.includes(ext),
|
if (/\b(html|css|js|jsx|ts|tsx|json|xml)\b/.test(lower)) {
|
||||||
)
|
|
||||||
) {
|
|
||||||
return <FileCode className={cn("size-5 text-emerald-500", className)} />;
|
return <FileCode className={cn("size-5 text-emerald-500", className)} />;
|
||||||
}
|
}
|
||||||
if (["xls", "xlsx", "csv"].some((ext) => ext.includes(ext))) {
|
|
||||||
|
// Spreadsheets
|
||||||
|
if (/\b(xls|xlsx|csv)\b/.test(lower)) {
|
||||||
return (
|
return (
|
||||||
<FileSpreadsheet className={cn("size-5 text-green-600", className)} />
|
<FileSpreadsheet className={cn("size-5 text-green-600", className)} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (["zip", "rar", "7z", "tar", "gz"].some((ext) => ext.includes(ext))) {
|
|
||||||
|
// Archives
|
||||||
|
if (/\b(zip|rar|7z|tar|gz)\b/.test(lower)) {
|
||||||
return <FileArchive className={cn("size-5 text-amber-600", className)} />;
|
return <FileArchive className={cn("size-5 text-amber-600", className)} />;
|
||||||
}
|
}
|
||||||
if (["mp3", "wav", "ogg", "flac"].some((ext) => ext.includes(ext))) {
|
|
||||||
|
// Audio
|
||||||
|
if (/\b(mp3|wav|ogg|flac)\b/.test(lower)) {
|
||||||
return <FileAudio className={cn("size-5 text-purple-500", className)} />;
|
return <FileAudio className={cn("size-5 text-purple-500", className)} />;
|
||||||
}
|
}
|
||||||
if (["mp4", "avi", "mov", "wmv", "webm"].some((ext) => ext.includes(ext))) {
|
|
||||||
|
// Video
|
||||||
|
if (/\b(mp4|avi|mov|wmv|webm)\b/.test(lower)) {
|
||||||
return <FileVideo className={cn("size-5 text-pink-500", className)} />;
|
return <FileVideo className={cn("size-5 text-pink-500", className)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <File className={cn("size-5 text-gray-500", className)} />;
|
return <File className={cn("size-5 text-gray-500", className)} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -235,7 +231,7 @@ const EditFileDialog = ({
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (status: boolean) => void;
|
onOpenChange: (status: boolean) => void;
|
||||||
data: {
|
data: {
|
||||||
id: StorageindexId;
|
id: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
} | null;
|
} | null;
|
||||||
handleEditSubmit: (e: FormEvent<HTMLFormElement>) => void;
|
handleEditSubmit: (e: FormEvent<HTMLFormElement>) => void;
|
||||||
|
|
@ -282,17 +278,15 @@ const EditFileDialog = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FileListProps {
|
interface FileListProps {
|
||||||
files: Storageindex[];
|
files: FileMetadata[];
|
||||||
delete_onClick: (id: StorageindexId) => void;
|
delete_onClick: (id: string) => void;
|
||||||
download_onClick: (file: Storageindex) => void;
|
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
selectHandler: (id: StorageindexId, filename: string) => void;
|
selectHandler: (id: string, filename: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FileList({
|
function FileList({
|
||||||
files,
|
files,
|
||||||
delete_onClick,
|
delete_onClick,
|
||||||
download_onClick,
|
|
||||||
isAdmin,
|
isAdmin,
|
||||||
selectHandler,
|
selectHandler,
|
||||||
}: FileListProps) {
|
}: FileListProps) {
|
||||||
|
|
@ -312,7 +306,7 @@ function FileList({
|
||||||
>
|
>
|
||||||
<div className="col-span-6 flex items-center gap-2 md:col-span-8">
|
<div className="col-span-6 flex items-center gap-2 md:col-span-8">
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<ExtIcon ext={file.ext} />
|
<ExtIcon mimeType={file.mimeType} />
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
aria-label="Allegato"
|
aria-label="Allegato"
|
||||||
|
|
@ -320,30 +314,23 @@ function FileList({
|
||||||
href={`/area-riservata/allegato-view/${file.id}`}
|
href={`/area-riservata/allegato-view/${file.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{file.filename}
|
{file.originalName}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-4 text-muted-foreground md:col-span-3">
|
<div className="col-span-4 text-muted-foreground md:col-span-3">
|
||||||
{format(file.created_at, "d MMM yyyy")}
|
{format(new Date(file.uploadedAt), "d MMM yyyy")}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 flex justify-end gap-1 md:col-span-1">
|
<div className="col-span-2 flex justify-end gap-1 md:col-span-1">
|
||||||
|
<Link href={`/storage-api/get/${file.id}?mode=download`}>
|
||||||
<Button
|
<Button
|
||||||
aria-label="Download"
|
className="flex size-8 gap-1 text-sm"
|
||||||
asChild
|
|
||||||
className="size-8"
|
|
||||||
size="icon"
|
size="icon"
|
||||||
title="Download"
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className="flex gap-1 text-sm"
|
|
||||||
onClick={() => download_onClick(file)}
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<Download className="size-4" />
|
<Download className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</Button>
|
</Link>
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
|
@ -357,7 +344,7 @@ function FileList({
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
aria-label="Rinomina"
|
aria-label="Rinomina"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
selectHandler(file.id, file.filename || "")
|
selectHandler(file.id, file.originalName || "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Pencil className="mr-2 size-4" />
|
<Pencil className="mr-2 size-4" />
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export const ChatAttachments = ({
|
||||||
data: attachments,
|
data: attachments,
|
||||||
isLoading: attachmentsLoading,
|
isLoading: attachmentsLoading,
|
||||||
refetch,
|
refetch,
|
||||||
} = api.storage.getUserStorage.useQuery(
|
} = api.storage.retrieveUserFileData.useQuery(
|
||||||
{ userId: chatUserData.id },
|
{ userId: chatUserData.id },
|
||||||
{ refetchOnMount: true },
|
{ refetchOnMount: true },
|
||||||
);
|
);
|
||||||
|
|
@ -58,20 +58,20 @@ export const ChatAttachments = ({
|
||||||
<Paperclip className="text-muted-foreground" size={20} />
|
<Paperclip className="text-muted-foreground" size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</CredenzaTrigger>
|
</CredenzaTrigger>
|
||||||
<CredenzaContent className="max-h-[90vh]">
|
<CredenzaContent className="max-h-[90vh] md:max-w-xl">
|
||||||
<CredenzaHeader>
|
<CredenzaHeader>
|
||||||
<CredenzaTitle className="sr-only">Allegati</CredenzaTitle>
|
<CredenzaTitle className="sr-only">Allegati</CredenzaTitle>
|
||||||
<CredenzaDescription className="sr-only">
|
<CredenzaDescription className="sr-only">
|
||||||
Chat Attachments
|
Chat Attachments
|
||||||
</CredenzaDescription>
|
</CredenzaDescription>
|
||||||
</CredenzaHeader>
|
</CredenzaHeader>
|
||||||
<CredenzaBody className="max-h-[80vh] space-y-3 overflow-auto pb-5">
|
<CredenzaBody className="max-h-[80vh] space-y-5 overflow-auto pb-5 md:px-1">
|
||||||
<UploadComponent
|
<UploadComponent
|
||||||
isAdmin={userData.isAdmin}
|
isAdmin={userData.isAdmin}
|
||||||
userId={chatUserData.id}
|
userId={chatUserData.id}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="h-auto max-h-[40rem] max-w-lg overflow-auto rounded-lg border-2 border-gray-300 py-4">
|
<div className="h-auto max-h-[40rem] max-w-lg overflow-auto rounded-lg">
|
||||||
{attachmentsLoading ? (
|
{attachmentsLoading ? (
|
||||||
<LoadingPage />
|
<LoadingPage />
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -84,7 +84,7 @@ export const ChatAttachments = ({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ul className="px-2">
|
<ul className="px-2">
|
||||||
<h3 className="px-3 font-semibold text-gray-700 text-lg">
|
<h3 className="font-semibold text-gray-700 text-lg">
|
||||||
{t.allegati.allegati_recenti}
|
{t.allegati.allegati_recenti}
|
||||||
</h3>
|
</h3>
|
||||||
{attachments?.map((file, idx) => {
|
{attachments?.map((file, idx) => {
|
||||||
|
|
@ -92,28 +92,30 @@ export const ChatAttachments = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className="flex items-center justify-between border-b px-3 py-1"
|
className="flex flex-row items-center justify-between border-b py-1"
|
||||||
key={`${file.filename}-${idx}`}
|
key={`${file.originalName}-${idx}`}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
aria-label="Visualizza Allegato"
|
aria-label="Visualizza Allegato"
|
||||||
className={cn(
|
|
||||||
buttonVariants({
|
|
||||||
variant: "link",
|
|
||||||
}),
|
|
||||||
"flex h-8 items-center gap-2 truncate p-0 text-base",
|
|
||||||
)}
|
|
||||||
href={`/area-riservata/allegato-view/${file.id}`}
|
href={`/area-riservata/allegato-view/${file.id}`}
|
||||||
|
onTouchStart={() => console.log("touching")}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{file.ext && <ExtIcon ext={file.ext} />}
|
<Button
|
||||||
<span className="truncate">{file.filename}</span>
|
className="flex h-8 items-center gap-2 truncate p-0 text-base has-[>svg]:px-0"
|
||||||
|
variant="link"
|
||||||
|
>
|
||||||
|
<ExtIcon mimeType={file.mimeType} />
|
||||||
|
<span className="max-w-[60vw] truncate">
|
||||||
|
{file.originalName}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<span
|
<span
|
||||||
className="text-muted-foreground text-xs"
|
className="text-muted-foreground text-xs"
|
||||||
key={new Date().toString()}
|
key={new Date().toString()}
|
||||||
>
|
>
|
||||||
{TimeSince(file.created_at)}
|
{TimeSince(new Date(file.uploadedAt))}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
@ -123,8 +125,6 @@ export const ChatAttachments = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CredenzaBody>
|
|
||||||
<CredenzaFooter className="flex flex-row items-center justify-between gap-2">
|
|
||||||
<Link
|
<Link
|
||||||
aria-label="Visualizza Allegati"
|
aria-label="Visualizza Allegati"
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -142,6 +142,8 @@ export const ChatAttachments = ({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
</CredenzaBody>
|
||||||
|
<CredenzaFooter className="flex flex-col items-center justify-between gap-2">
|
||||||
<CredenzaClose asChild>
|
<CredenzaClose asChild>
|
||||||
<Button className="w-full">{t.chiudi}</Button>
|
<Button className="w-full">{t.chiudi}</Button>
|
||||||
</CredenzaClose>
|
</CredenzaClose>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -63,11 +63,8 @@ import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
|
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
|
||||||
import type {
|
|
||||||
Storageindex,
|
|
||||||
StorageindexId,
|
|
||||||
} from "~/schemas/public/Storageindex";
|
|
||||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
|
import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export const AnnuncioActions = () => {
|
export const AnnuncioActions = () => {
|
||||||
|
|
@ -495,7 +492,7 @@ const ContrattoSection = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { userId } = useServizio();
|
const { userId } = useServizio();
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
const [selectedDoc, setSelectedDoc] = useState<StorageindexId | null>(
|
const [selectedDoc, setSelectedDoc] = useState<string | null>(
|
||||||
data.doc_contratto_ref,
|
data.doc_contratto_ref,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -505,7 +502,7 @@ const ContrattoSection = ({
|
||||||
|
|
||||||
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.storage.getUserStorage.invalidate({
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
await utils.storage.getAll.invalidate();
|
await utils.storage.getAll.invalidate();
|
||||||
|
|
@ -519,7 +516,7 @@ const ContrattoSection = ({
|
||||||
});
|
});
|
||||||
if (selectedDoc) {
|
if (selectedDoc) {
|
||||||
setUserStorage({
|
setUserStorage({
|
||||||
fileId: selectedDoc,
|
storageId: selectedDoc,
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -587,7 +584,7 @@ const RicevutaSection = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { userId } = useServizio();
|
const { userId } = useServizio();
|
||||||
const { data } = useServizioAnnuncio();
|
const { data } = useServizioAnnuncio();
|
||||||
const [selectedDoc, setSelectedDoc] = useState<StorageindexId | null>(
|
const [selectedDoc, setSelectedDoc] = useState<string | null>(
|
||||||
data.doc_registrazione_ref,
|
data.doc_registrazione_ref,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -597,7 +594,7 @@ const RicevutaSection = ({
|
||||||
|
|
||||||
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.storage.getUserStorage.invalidate({
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
await utils.storage.getAll.invalidate();
|
await utils.storage.getAll.invalidate();
|
||||||
|
|
@ -611,7 +608,7 @@ const RicevutaSection = ({
|
||||||
});
|
});
|
||||||
if (selectedDoc) {
|
if (selectedDoc) {
|
||||||
setUserStorage({
|
setUserStorage({
|
||||||
fileId: selectedDoc,
|
storageId: selectedDoc,
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -762,7 +759,7 @@ const DocSection = ({
|
||||||
|
|
||||||
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await utils.storage.getUserStorage.invalidate({
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
await utils.storage.getAll.invalidate();
|
await utils.storage.getAll.invalidate();
|
||||||
|
|
@ -812,7 +809,7 @@ const DocSection = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
setUserStorage({
|
setUserStorage({
|
||||||
fileId: f.id,
|
storageId: f.id,
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
@ -828,10 +825,10 @@ const DocInfo = ({
|
||||||
storageId,
|
storageId,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
storageId: StorageindexId;
|
storageId: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const { data: file_info } = api.storage.getStorageFromId.useQuery({
|
const { data: file_info } = api.storage.getFileInfos.useQuery({
|
||||||
storageId,
|
storageId,
|
||||||
});
|
});
|
||||||
if (!file_info) return null;
|
if (!file_info) return null;
|
||||||
|
|
@ -852,9 +849,9 @@ const DocInfo = ({
|
||||||
id="selected-file"
|
id="selected-file"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{file_info.ext && <ExtIcon ext={file_info.ext} />}
|
<ExtIcon mimeType={file_info.mimeType} />
|
||||||
<span className="max-w-40 truncate sm:max-w-80">
|
<span className="max-w-40 truncate sm:max-w-80">
|
||||||
{file_info.filename}
|
{file_info.originalName}
|
||||||
</span>
|
</span>
|
||||||
<ExternalLink className="size-4" />
|
<ExternalLink className="size-4" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -870,9 +867,9 @@ const DocCombo = ({
|
||||||
onSelect,
|
onSelect,
|
||||||
current,
|
current,
|
||||||
}: {
|
}: {
|
||||||
files: Storageindex[];
|
files: FileMetadataWithAdmin[];
|
||||||
onSelect: (file: Storageindex) => void;
|
onSelect: (file: FileMetadataWithAdmin) => void;
|
||||||
current: StorageindexId | null;
|
current: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
|
@ -909,8 +906,10 @@ const DocCombo = ({
|
||||||
value={file.id}
|
value={file.id}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
{file.ext && <ExtIcon ext={file.ext} />}
|
<ExtIcon mimeType={file.mimeType} />
|
||||||
<span className="block font-medium">{file.filename}</span>
|
<span className="block font-medium">
|
||||||
|
{file.originalName}
|
||||||
|
</span>
|
||||||
{current === file.id && (
|
{current === file.id && (
|
||||||
<Check className="size-4 text-accent-foreground" />
|
<Check className="size-4 text-accent-foreground" />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,29 @@
|
||||||
|
import Link from "next/link";
|
||||||
import { AllegatoIframe } from "~/components/allegato-iframe";
|
import { AllegatoIframe } from "~/components/allegato-iframe";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { handleDownload } from "~/hooks/filesHooks";
|
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type { StorageindexId } from "~/schemas/public/Storageindex";
|
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export const FileSection = ({ storageId }: { storageId: StorageindexId }) => {
|
export const FileSection = ({ storageId }: { storageId: string }) => {
|
||||||
const { data: fileInfos, isLoading } = api.storage.getStorageFromId.useQuery({
|
const { data: fileInfos, isLoading } = api.storage.getFileInfos.useQuery({
|
||||||
storageId,
|
storageId,
|
||||||
});
|
});
|
||||||
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
if (isLoading) return <LoadingPage />;
|
if (isLoading) return <LoadingPage />;
|
||||||
if (!fileInfos) return <p>{t.file_section.error}</p>;
|
if (!fileInfos) return <p>{t.file_section.error}</p>;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AllegatoIframe
|
<AllegatoIframe
|
||||||
allegato={fileInfos.id + fileInfos.ext}
|
allegato={fileInfos}
|
||||||
className="h-full max-h-[70vh] min-h-[60vh]"
|
className="h-full max-h-[70vh] min-h-[60vh]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Link
|
||||||
className="underline underline-offset-1 after:content-['_↗']"
|
className="underline underline-offset-1 after:content-['_↗']"
|
||||||
onClick={() =>
|
href={`/storage-api/get/${fileInfos.id}?mode=download`}
|
||||||
handleDownload({
|
|
||||||
file: fileInfos,
|
|
||||||
getToken: async () => await getToken(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="link"
|
|
||||||
>
|
>
|
||||||
{t.file_section.download}
|
{t.file_section.download}
|
||||||
</Button>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -38,28 +38,27 @@ import { Input } from "~/components/ui/input";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useStorageTable } from "~/providers/StorageTableProvider";
|
import { useStorageTable } from "~/providers/StorageTableProvider";
|
||||||
import type {
|
import {
|
||||||
Storageindex,
|
editFile,
|
||||||
StorageindexId,
|
type FileMetadataWithAdmin,
|
||||||
} from "~/schemas/public/Storageindex";
|
} from "~/server/services/storage.service";
|
||||||
|
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export const StorageTable = ({
|
export const StorageTable = ({
|
||||||
data,
|
data,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
}: {
|
}: {
|
||||||
data: Storageindex[];
|
data: FileMetadataWithAdmin[];
|
||||||
|
|
||||||
handleDelete: (id: StorageindexId) => void;
|
handleDelete: (id: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { setTable, cb_onStateChange } = useStorageTable();
|
const { setTable, cb_onStateChange } = useStorageTable();
|
||||||
|
|
||||||
const [deleteConfirmId, setDeleteConfirmId] = useState<StorageindexId | null>(
|
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [renameTarget, setRenameTarget] = useState<{
|
const [renameTarget, setRenameTarget] = useState<{
|
||||||
allegatoId: StorageindexId;
|
allegatoId: string;
|
||||||
filename: string | null;
|
filename: string | null;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
|
|
@ -74,23 +73,13 @@ export const StorageTable = ({
|
||||||
}
|
}
|
||||||
}, [tableInstance, setTable]);
|
}, [tableInstance, setTable]);
|
||||||
|
|
||||||
const tabledata = data.map((row) => {
|
const tabledata = data;
|
||||||
return {
|
|
||||||
actions: "Azioni",
|
|
||||||
created_at: row.created_at,
|
|
||||||
expires_at: row.expires_at,
|
|
||||||
ext: row.ext,
|
|
||||||
filename: row.filename,
|
|
||||||
from_admin: row.from_admin,
|
|
||||||
id: row.id,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns_titles = {
|
const columns_titles = {
|
||||||
actions: "Azioni",
|
actions: "Azioni",
|
||||||
created_at: "Data creazione",
|
uploadedAt: "Data creazione",
|
||||||
ext: "Estensione",
|
type: "Tipo file",
|
||||||
filename: "Nome file",
|
originalName: "Nome file",
|
||||||
from_admin: "Caricato da",
|
from_admin: "Caricato da",
|
||||||
id: "Codice",
|
id: "Codice",
|
||||||
};
|
};
|
||||||
|
|
@ -120,7 +109,7 @@ export const StorageTable = ({
|
||||||
id: "select",
|
id: "select",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "filename",
|
accessorKey: "originalName",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -129,7 +118,7 @@ export const StorageTable = ({
|
||||||
href={`/area-riservata/allegato-view/${row.original.id}`}
|
href={`/area-riservata/allegato-view/${row.original.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{row.original.filename}
|
{row.original.originalName}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -137,14 +126,14 @@ export const StorageTable = ({
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader
|
<DataTableColumnHeader
|
||||||
column={column}
|
column={column}
|
||||||
title={columns_titles.filename}
|
title={columns_titles.originalName}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "created_at",
|
accessorKey: "uploadedAt",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const date = row.getValue("created_at");
|
const date = row.getValue("uploadedAt");
|
||||||
return new Date(date as Date).toLocaleDateString();
|
return new Date(date as Date).toLocaleDateString();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -152,7 +141,7 @@ export const StorageTable = ({
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader
|
<DataTableColumnHeader
|
||||||
column={column}
|
column={column}
|
||||||
title={columns_titles.created_at}
|
title={columns_titles.uploadedAt}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -203,7 +192,7 @@ export const StorageTable = ({
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setRenameTarget({
|
setRenameTarget({
|
||||||
allegatoId: data.id,
|
allegatoId: data.id,
|
||||||
filename: data.filename,
|
filename: data.originalName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
role="button"
|
role="button"
|
||||||
|
|
@ -231,7 +220,7 @@ export const StorageTable = ({
|
||||||
];
|
];
|
||||||
|
|
||||||
const searchFiltro: SearchFiltro = {
|
const searchFiltro: SearchFiltro = {
|
||||||
columnName: "filename",
|
columnName: "originalName",
|
||||||
placeholder: "Cerca file ...",
|
placeholder: "Cerca file ...",
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
@ -240,7 +229,7 @@ export const StorageTable = ({
|
||||||
columns={columns}
|
columns={columns}
|
||||||
columns_titles={columns_titles}
|
columns_titles={columns_titles}
|
||||||
data={tabledata}
|
data={tabledata}
|
||||||
defaultSort={[{ desc: true, id: "created_at" }]}
|
defaultSort={[{ desc: true, id: "uploadedAt" }]}
|
||||||
onStateChange={cb_onStateChange}
|
onStateChange={cb_onStateChange}
|
||||||
onTableInit={setTableInstance}
|
onTableInit={setTableInstance}
|
||||||
pinnedFiltri={undefined}
|
pinnedFiltri={undefined}
|
||||||
|
|
@ -289,42 +278,32 @@ const RinominaDialog = ({
|
||||||
renameTarget,
|
renameTarget,
|
||||||
setRenameTarget,
|
setRenameTarget,
|
||||||
}: {
|
}: {
|
||||||
renameTarget: { allegatoId: StorageindexId; filename: string | null };
|
renameTarget: { allegatoId: string; filename: string | null };
|
||||||
setRenameTarget: (
|
setRenameTarget: (
|
||||||
target: { allegatoId: StorageindexId; filename: string | null } | null,
|
target: { allegatoId: string; filename: string | null } | null,
|
||||||
) => void;
|
) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const { mutate: renameFile } = api.storage.renameFile.useMutation({
|
const handleEditSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
onMutate: () => {
|
|
||||||
setRenameTarget(null);
|
|
||||||
const toastId = toast.loading(t.caricamento, {
|
|
||||||
icon: "🪛",
|
|
||||||
});
|
|
||||||
return { toastId };
|
|
||||||
},
|
|
||||||
onSuccess: async (_error, _variables, context) => {
|
|
||||||
await utils.storage.getAll.invalidate();
|
|
||||||
toast.success(t.successo, {
|
|
||||||
icon: "👍",
|
|
||||||
id: context?.toastId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleEditSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const form = e.currentTarget;
|
const form = e.currentTarget;
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
if (!formData.get("filename")) {
|
if (!formData.get("filename")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renameFile({
|
const rename = await editFile(
|
||||||
fileId: renameTarget.allegatoId,
|
renameTarget.allegatoId,
|
||||||
name: formData.get("filename") as string,
|
formData.get("filename") as string,
|
||||||
});
|
);
|
||||||
|
if (rename.success) {
|
||||||
|
setRenameTarget(null);
|
||||||
|
toast.success("File rinominato con successo", { icon: "👍" });
|
||||||
|
await utils.storage.getAll.invalidate();
|
||||||
|
} else {
|
||||||
|
toast.error(rename.error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ const buttonVariants = cva(
|
||||||
grey: "bg-accent/30 text-accent-foreground shadow-sm hover:bg-accent/80 dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white",
|
grey: "bg-accent/30 text-accent-foreground shadow-sm hover:bg-accent/80 dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white",
|
||||||
info: "bg-sky-500 text-white hover:bg-sky-500/90 dark:bg-sky-900 dark:text-white dark:hover:bg-sky-900/90",
|
info: "bg-sky-500 text-white hover:bg-sky-500/90 dark:bg-sky-900 dark:text-white dark:hover:bg-sky-900/90",
|
||||||
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline active:underline",
|
||||||
orange:
|
orange:
|
||||||
"bg-orange-500 text-white hover:bg-orange-500/90 dark:bg-orange-900 dark:text-white dark:hover:bg-orange-900/90",
|
"bg-orange-500 text-white hover:bg-orange-500/90 dark:bg-orange-900 dark:text-white dark:hover:bg-orange-900/90",
|
||||||
outline:
|
outline:
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,8 @@ import {
|
||||||
FileUploadTrigger,
|
FileUploadTrigger,
|
||||||
} from "~/components/custom_ui/fileUpload";
|
} from "~/components/custom_ui/fileUpload";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Upload } from "~/lib/storage_manager";
|
import { uploadHandler } from "~/hooks/storageClienSideHooks";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type { StorageindexId } from "~/schemas/public/Storageindex";
|
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
|
|
@ -88,15 +87,14 @@ export const UploadComponent = ({
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
|
||||||
const { mutateAsync: getToken } = api.storage.getStorageTokenM.useMutation();
|
const { mutateAsync: setUserStorage } =
|
||||||
|
api.storage.addUserStorage.useMutation({
|
||||||
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
|
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
toast(t.allegati.allegato_caricato, { icon: "📤" });
|
toast(t.allegati.allegato_caricato, { icon: "📤" });
|
||||||
},
|
},
|
||||||
onSettled: async () => {
|
onSettled: async () => {
|
||||||
await utils.storage.getAll.invalidate();
|
await utils.storage.getAll.invalidate();
|
||||||
await utils.storage.getUserStorage.invalidate({
|
await utils.storage.retrieveUserFileData.invalidate({
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -159,19 +157,24 @@ export const UploadComponent = ({
|
||||||
onProgress: (file: File, progress: number) => void;
|
onProgress: (file: File, progress: number) => void;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
|
if (files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
const from_admin = isAdmin ?? true;
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
const { status, attachments } = await Upload({
|
|
||||||
files: files,
|
const { status, attachments } = await uploadHandler({
|
||||||
from_admin: isAdmin ?? true,
|
files,
|
||||||
getToken,
|
from_admin,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
for (const storageId of attachments) {
|
for (const storageId of attachments) {
|
||||||
setUserStorage({
|
await setUserStorage({
|
||||||
fileId: storageId as StorageindexId,
|
storageId,
|
||||||
userId,
|
userId,
|
||||||
|
from_admin,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ export const env = createEnv({
|
||||||
SKEBBY_USER: z.string(),
|
SKEBBY_USER: z.string(),
|
||||||
SKEBBY_PASS: z.string(),
|
SKEBBY_PASS: z.string(),
|
||||||
REVALIDATION_SECRET: z.string(),
|
REVALIDATION_SECRET: z.string(),
|
||||||
|
STORAGE_URL: z.string(),
|
||||||
STORAGE_TOKEN: z.string(),
|
STORAGE_TOKEN: z.string(),
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import type { Storageindex } from "~/schemas/public/Storageindex";
|
|
||||||
import type { TempTokensToken } from "~/schemas/public/TempTokens";
|
|
||||||
|
|
||||||
export const handleDownload = async ({
|
|
||||||
file,
|
|
||||||
getToken,
|
|
||||||
}: {
|
|
||||||
file: Storageindex;
|
|
||||||
getToken: () => Promise<TempTokensToken | TempTokensToken[]>;
|
|
||||||
}) => {
|
|
||||||
const auth_token = (await getToken()) as TempTokensToken;
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`/go-api/storage/get/${file.id}${file.ext}?token=${auth_token}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("File fetch failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = await response.blob();
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = url;
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: <known to be non-null>
|
|
||||||
a.download = file.filename!;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
} catch {
|
|
||||||
throw new Error("File download failed");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
83
apps/infoalloggi/src/hooks/storageClienSideHooks.ts
Normal file
83
apps/infoalloggi/src/hooks/storageClienSideHooks.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { add } from "date-fns";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
type UploadResponse =
|
||||||
|
| {
|
||||||
|
success: true;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
// Helper to handle file uploads
|
||||||
|
export async function uploadFile(
|
||||||
|
file: File,
|
||||||
|
expiresAt: string | undefined,
|
||||||
|
): Promise<UploadResponse> {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
if (expiresAt) {
|
||||||
|
formData.append("expires_at", expiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/storage-api/upload`, {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const res = await response.json();
|
||||||
|
const parsed = z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
})
|
||||||
|
.parse(res);
|
||||||
|
const fileID = parsed.id;
|
||||||
|
console.log("File uploaded successfully.");
|
||||||
|
return { success: true, id: fileID };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Caricamento fallito con status: ${response.status}`,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Errore caricamento file: ${(e as Error).message}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadHandler = async ({
|
||||||
|
files,
|
||||||
|
from_admin,
|
||||||
|
}: {
|
||||||
|
files: File[];
|
||||||
|
from_admin: boolean;
|
||||||
|
}) => {
|
||||||
|
const attachments = [];
|
||||||
|
const failed: string[] = [];
|
||||||
|
const expirationDate = add(new Date(), { days: 60 }).toISOString();
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.error("No files provided");
|
||||||
|
return { attachments: [], error: "No files provided", status: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const upload = await uploadFile(
|
||||||
|
file,
|
||||||
|
from_admin ? expirationDate : undefined,
|
||||||
|
);
|
||||||
|
if (upload.success) {
|
||||||
|
attachments.push(upload.id);
|
||||||
|
} else {
|
||||||
|
console.error(`Error uploading file ${file.name}: ${upload.error}`);
|
||||||
|
failed.push(file.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { attachments, failed, status: true };
|
||||||
|
};
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
import type { TempTokensToken } from "~/schemas/public/TempTokens";
|
|
||||||
|
|
||||||
export const Upload = async ({
|
|
||||||
files,
|
|
||||||
getToken,
|
|
||||||
from_admin,
|
|
||||||
}: {
|
|
||||||
files: File[];
|
|
||||||
getToken: ({ num }: { num: number }) => Promise<TempTokensToken[]>;
|
|
||||||
from_admin: boolean;
|
|
||||||
}) => {
|
|
||||||
const attachments = [];
|
|
||||||
if (files.length === 0) {
|
|
||||||
console.error("No files provided");
|
|
||||||
return { attachments: [], error: "No files provided", status: false };
|
|
||||||
}
|
|
||||||
if (files.length > 1) {
|
|
||||||
const authTokens = await getToken({
|
|
||||||
num: files.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (authTokens.length !== files.length) {
|
|
||||||
console.error(
|
|
||||||
"Error: number of tokens does not match number of files",
|
|
||||||
authTokens.length,
|
|
||||||
files.length,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
attachments: [],
|
|
||||||
error: "Error: number of tokens does not match number of files",
|
|
||||||
status: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
for (const file of files) {
|
|
||||||
const attach = await uploadFile({
|
|
||||||
file,
|
|
||||||
from_admin,
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: <intended>
|
|
||||||
token: authTokens.pop()!,
|
|
||||||
});
|
|
||||||
attachments.push(attach);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const authTokens = (
|
|
||||||
await getToken({
|
|
||||||
num: 1,
|
|
||||||
})
|
|
||||||
)[0];
|
|
||||||
if (!authTokens) {
|
|
||||||
console.error("Error: no token received");
|
|
||||||
return {
|
|
||||||
attachments: [],
|
|
||||||
error: "Error: no token received",
|
|
||||||
status: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const attach = await uploadFile({
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: <intended>
|
|
||||||
file: files[0]!,
|
|
||||||
from_admin,
|
|
||||||
token: authTokens,
|
|
||||||
});
|
|
||||||
attachments.push(attach);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { attachments, status: true };
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadFile = async ({
|
|
||||||
file,
|
|
||||||
token,
|
|
||||||
from_admin,
|
|
||||||
}: {
|
|
||||||
file: File;
|
|
||||||
token: TempTokensToken;
|
|
||||||
from_admin: boolean;
|
|
||||||
}) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", file);
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`/go-api/storage/upload?token=${token}&from_admin=${from_admin}`,
|
|
||||||
{
|
|
||||||
body: formData,
|
|
||||||
method: "POST",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(
|
|
||||||
`Error uploading file: ${response.status} ${response.statusText}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return response.text();
|
|
||||||
};
|
|
||||||
|
|
@ -38,10 +38,7 @@ import {
|
||||||
type StorageTable as StorageTableType,
|
type StorageTable as StorageTableType,
|
||||||
useStorageTable,
|
useStorageTable,
|
||||||
} from "~/providers/StorageTableProvider";
|
} from "~/providers/StorageTableProvider";
|
||||||
import type {
|
|
||||||
Storageindex,
|
|
||||||
StorageindexId,
|
|
||||||
} from "~/schemas/public/Storageindex";
|
|
||||||
import type { Users } from "~/schemas/public/Users";
|
import type { Users } from "~/schemas/public/Users";
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
@ -50,7 +47,7 @@ const AdminStorage: NextPageWithLayout = () => {
|
||||||
const { data, isLoading, refetch } = api.storage.getAll.useQuery();
|
const { data, isLoading, refetch } = api.storage.getAll.useQuery();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutate: deleteFile } = api.storage.deleteStorage.useMutation({
|
const { mutate: deleteFiles } = api.storage.deleteFile.useMutation({
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
const toastId = toast.loading(t.caricamento, {
|
const toastId = toast.loading(t.caricamento, {
|
||||||
icon: "🪛",
|
icon: "🪛",
|
||||||
|
|
@ -67,8 +64,8 @@ const AdminStorage: NextPageWithLayout = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = (id: StorageindexId) => {
|
const handleDelete = (id: string) => {
|
||||||
deleteFile({ storageId: id });
|
deleteFiles({ storageId: id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefetch = async () => {
|
const handleRefetch = async () => {
|
||||||
|
|
@ -117,7 +114,7 @@ const StorageTableHeader = ({
|
||||||
handleDelete,
|
handleDelete,
|
||||||
}: {
|
}: {
|
||||||
handleRefetch: () => Promise<void>;
|
handleRefetch: () => Promise<void>;
|
||||||
handleDelete: (id: StorageindexId) => void;
|
handleDelete: (id: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { table } = useStorageTable();
|
const { table } = useStorageTable();
|
||||||
if (!table) return null;
|
if (!table) return null;
|
||||||
|
|
@ -158,7 +155,7 @@ const Actions = ({
|
||||||
handleDelete,
|
handleDelete,
|
||||||
table,
|
table,
|
||||||
}: {
|
}: {
|
||||||
handleDelete: (id: StorageindexId) => void;
|
handleDelete: (id: string) => void;
|
||||||
table: StorageTableType;
|
table: StorageTableType;
|
||||||
}) => {
|
}) => {
|
||||||
const { selectedRows } = useStorageTable();
|
const { selectedRows } = useStorageTable();
|
||||||
|
|
@ -190,9 +187,9 @@ const Actions = ({
|
||||||
</Button>
|
</Button>
|
||||||
{visbOpen && (
|
{visbOpen && (
|
||||||
<VisibComponent
|
<VisibComponent
|
||||||
allegati={selectedRows}
|
|
||||||
open={visbOpen}
|
open={visbOpen}
|
||||||
setOpen={setVisbOpen}
|
setOpen={setVisbOpen}
|
||||||
|
storageIds={selectedRows.map((r) => r.id)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -200,21 +197,21 @@ const Actions = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const VisibComponent = ({
|
const VisibComponent = ({
|
||||||
allegati,
|
storageIds,
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
}: {
|
}: {
|
||||||
allegati: Storageindex[];
|
storageIds: string[];
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (v: boolean) => void;
|
setOpen: (v: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { data, isLoading } = api.storage.getStorageVisibility.useQuery({
|
const { data, isLoading } = api.storage.getStorageVisibility.useQuery({
|
||||||
storageIds: allegati.map((a) => a.id),
|
storageIds,
|
||||||
});
|
});
|
||||||
const { data: users } = api.users.getUsers.useQuery();
|
const { data: users } = api.users.getUsers.useQuery();
|
||||||
|
|
||||||
const { mutate: remove } = api.storage.removeStorageVisibility.useMutation({
|
const { mutate: remove } = api.storage.deleteUserStorageEntry.useMutation({
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
const toastId = toast.loading("Rimozione utente in corso", {
|
const toastId = toast.loading("Rimozione utente in corso", {
|
||||||
icon: "🪛",
|
icon: "🪛",
|
||||||
|
|
@ -229,7 +226,7 @@ const VisibComponent = ({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { mutate: add } = api.storage.addStorageVisibility.useMutation({
|
const { mutate: add } = api.storage.addUserStorage.useMutation({
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
const toastId = toast.loading("Aggiunta utente in corso", {
|
const toastId = toast.loading("Aggiunta utente in corso", {
|
||||||
icon: "🪛",
|
icon: "🪛",
|
||||||
|
|
@ -245,7 +242,7 @@ const VisibComponent = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: reset } = api.storage.resetStorageVisibility.useMutation({
|
const { mutate: reset } = api.storage.deleteFilesFromUserStorage.useMutation({
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
const toastId = toast.loading("Reset visibilità in corso", {
|
const toastId = toast.loading("Reset visibilità in corso", {
|
||||||
icon: "🪛",
|
icon: "🪛",
|
||||||
|
|
@ -280,17 +277,19 @@ const VisibComponent = ({
|
||||||
<p>Seleziona utente:</p>
|
<p>Seleziona utente:</p>
|
||||||
<UtentiCombo
|
<UtentiCombo
|
||||||
onSelect={(user) => {
|
onSelect={(user) => {
|
||||||
for (const allegato of allegati) {
|
for (const allegato of storageIds) {
|
||||||
add({ storageId: allegato.id, userId: user.id });
|
add({
|
||||||
|
storageId: allegato,
|
||||||
|
userId: user.id,
|
||||||
|
from_admin: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
users={users || []}
|
users={users || []}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
onClick={() =>
|
onClick={() => reset({ storageIds })}
|
||||||
reset({ storageId: allegati.map((a) => a.id) })
|
|
||||||
}
|
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
|
|
@ -305,22 +304,22 @@ const VisibComponent = ({
|
||||||
key={allegato.id}
|
key={allegato.id}
|
||||||
>
|
>
|
||||||
<p className="font-semibold text-lg">
|
<p className="font-semibold text-lg">
|
||||||
File: {allegato.filename}
|
File: {allegato.originalName}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<p>Visibile a:</p>
|
<p>Visibile a:</p>
|
||||||
{allegato.users_storages.length === 0 ? (
|
{allegato.users.length === 0 ? (
|
||||||
<p>Nessuno</p>
|
<p>Nessuno</p>
|
||||||
) : (
|
) : (
|
||||||
<ul className="flex gap-2">
|
<ul className="flex gap-2">
|
||||||
{allegato.users_storages.map((user) => (
|
{allegato.users.map((user) => (
|
||||||
<li key={user.userid}>
|
<li key={user.id}>
|
||||||
<Button
|
<Button
|
||||||
className="after:content-['X']"
|
className="after:content-['X']"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
remove({
|
remove({
|
||||||
storageId: allegato.id,
|
storageId: allegato.id,
|
||||||
userId: user.userid,
|
userId: user.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export const getServerSideProps = (async (context) => {
|
||||||
|
|
||||||
const helpers = await TrpcAuthedFetchingIstance({ access_token });
|
const helpers = await TrpcAuthedFetchingIstance({ access_token });
|
||||||
if (helpers) {
|
if (helpers) {
|
||||||
await helpers.trpc.storage.getUserStorage.prefetch({
|
await helpers.trpc.storage.retrieveUserFileData.prefetch({
|
||||||
userId: parsed.data,
|
userId: parsed.data,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { Download, Pen, Trash } from "lucide-react";
|
import { Download, Pen, Trash } from "lucide-react";
|
||||||
import type { GetServerSideProps } from "next";
|
import type { GetServerSideProps } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { type FormEvent, useState } from "react";
|
import { type FormEvent, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import z from "zod";
|
||||||
import { AllegatoIframe } from "~/components/allegato-iframe";
|
import { AllegatoIframe } from "~/components/allegato-iframe";
|
||||||
import { Confirm } from "~/components/confirm";
|
import { Confirm } from "~/components/confirm";
|
||||||
import { DocNotFoundPage } from "~/components/doc_not_found";
|
import { DocNotFoundPage } from "~/components/doc_not_found";
|
||||||
|
|
@ -19,17 +21,14 @@ import {
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "~/components/ui/dialog";
|
} from "~/components/ui/dialog";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { handleDownload } from "~/hooks/filesHooks";
|
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { useEnforcedSession } from "~/providers/SessionProvider";
|
import { useEnforcedSession } from "~/providers/SessionProvider";
|
||||||
import type { StorageindexId } from "~/schemas/public/Storageindex";
|
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
import { zStorageIndexId } from "~/server/utils/zod_types";
|
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
type AllegatoViewProps = {
|
type AllegatoViewProps = {
|
||||||
allegatoId: StorageindexId;
|
allegatoId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
||||||
|
|
@ -37,13 +36,12 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
||||||
}: AllegatoViewProps) => {
|
}: AllegatoViewProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const session = useEnforcedSession();
|
const session = useEnforcedSession();
|
||||||
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
|
const { data: fileInfos, isLoading } = api.storage.getFileInfos.useQuery({
|
||||||
const { data: fileInfos, isLoading } = api.storage.getStorageFromId.useQuery({
|
|
||||||
storageId: allegatoId,
|
storageId: allegatoId,
|
||||||
});
|
});
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutate: deleteFile } = api.storage.deleteStorage.useMutation({
|
const { mutate: deleteFile } = api.storage.deleteFile.useMutation({
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
const toastId = toast.loading(t.caricamento, {
|
const toastId = toast.loading(t.caricamento, {
|
||||||
icon: "🪛",
|
icon: "🪛",
|
||||||
|
|
@ -51,7 +49,7 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
||||||
return { toastId };
|
return { toastId };
|
||||||
},
|
},
|
||||||
onSuccess: async (_error, _variables, context) => {
|
onSuccess: async (_error, _variables, context) => {
|
||||||
await utils.storage.getUserStorage.invalidate();
|
await utils.storage.retrieveUserFileData.invalidate();
|
||||||
|
|
||||||
toast.success(t.allegati.allegato_rimosso, {
|
toast.success(t.allegati.allegato_rimosso, {
|
||||||
icon: "🗑️",
|
icon: "🗑️",
|
||||||
|
|
@ -69,29 +67,25 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Infoalloggi.it | {fileInfos.filename}</title>
|
<title>Infoalloggi.it | {fileInfos.originalName}</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<div className="flex h-full w-full grow flex-col md:flex-row">
|
<div className="flex h-full w-full grow flex-col md:flex-row">
|
||||||
<div className="flex grow flex-col justify-center space-y-2 p-4">
|
<div className="flex grow flex-col justify-center space-y-2 p-4">
|
||||||
<div className="mx-auto flex flex-wrap items-center gap-4">
|
<div className="mx-auto flex flex-wrap items-center gap-4">
|
||||||
<span>{fileInfos.filename}</span>
|
<span>{fileInfos.originalName}</span>
|
||||||
<span>caricato il {fileInfos.created_at.toLocaleString("it")}</span>
|
<span>
|
||||||
<Button
|
caricato il {new Date(fileInfos.uploadedAt).toLocaleString("it")}
|
||||||
className="flex gap-2"
|
</span>
|
||||||
onClick={() =>
|
<Link href={`/storage-api/get/${fileInfos.id}?mode=download`}>
|
||||||
handleDownload({
|
<Button className="flex gap-2">
|
||||||
file: fileInfos,
|
|
||||||
getToken: async () => await getToken(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Download size={16} />
|
<Download size={16} />
|
||||||
Download
|
Download
|
||||||
</Button>
|
</Button>
|
||||||
|
</Link>
|
||||||
<RinominaDialog
|
<RinominaDialog
|
||||||
allegatoId={allegatoId}
|
allegatoId={allegatoId}
|
||||||
filename={fileInfos.filename}
|
filename={fileInfos.originalName}
|
||||||
/>
|
/>
|
||||||
{session.user.isAdmin && (
|
{session.user.isAdmin && (
|
||||||
<Confirm
|
<Confirm
|
||||||
|
|
@ -106,7 +100,7 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
|
||||||
</Confirm>
|
</Confirm>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<AllegatoIframe allegato={fileInfos.id + fileInfos.ext} />
|
<AllegatoIframe allegato={fileInfos} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
@ -125,7 +119,7 @@ export const getServerSideProps = (async (context) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedStorageId = zStorageIndexId.safeParse(allegatoId);
|
const parsedStorageId = z.string().safeParse(allegatoId);
|
||||||
|
|
||||||
if (!parsedStorageId.success) {
|
if (!parsedStorageId.success) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -137,7 +131,7 @@ export const getServerSideProps = (async (context) => {
|
||||||
|
|
||||||
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
||||||
if (helper) {
|
if (helper) {
|
||||||
await helper.trpc.storage.getStorageFromId.prefetch({
|
await helper.trpc.storage.getFileInfos.prefetch({
|
||||||
storageId: parsedStorageId.data,
|
storageId: parsedStorageId.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -162,7 +156,7 @@ const RinominaDialog = ({
|
||||||
allegatoId,
|
allegatoId,
|
||||||
filename,
|
filename,
|
||||||
}: {
|
}: {
|
||||||
allegatoId: StorageindexId;
|
allegatoId: string;
|
||||||
filename: string | null;
|
filename: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -178,7 +172,7 @@ const RinominaDialog = ({
|
||||||
return { toastId };
|
return { toastId };
|
||||||
},
|
},
|
||||||
onSuccess: async (_error, _variables, context) => {
|
onSuccess: async (_error, _variables, context) => {
|
||||||
await utils.storage.getStorageFromId.invalidate({
|
await utils.storage.getFileInfos.invalidate({
|
||||||
storageId: allegatoId,
|
storageId: allegatoId,
|
||||||
});
|
});
|
||||||
toast.success(t.successo, {
|
toast.success(t.successo, {
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ const ConfirmSection = ({
|
||||||
return { toastId };
|
return { toastId };
|
||||||
},
|
},
|
||||||
onSuccess: async (_error, _variables, context) => {
|
onSuccess: async (_error, _variables, context) => {
|
||||||
await utils.storage.getUserStorage.invalidate();
|
await utils.storage.retrieveUserFileData.invalidate();
|
||||||
|
|
||||||
toast.success("Confermato", {
|
toast.success("Confermato", {
|
||||||
icon: "👍",
|
icon: "👍",
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
import type { GetServerSideProps } from "next";
|
import type { GetServerSideProps } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
import { AllegatoIframe } from "~/components/allegato-iframe";
|
import { AllegatoIframe } from "~/components/allegato-iframe";
|
||||||
import { DocNotFoundPage } from "~/components/doc_not_found";
|
import { DocNotFoundPage } from "~/components/doc_not_found";
|
||||||
import { AreaRiservataLayout } from "~/components/Layout";
|
import { AreaRiservataLayout } from "~/components/Layout";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { Status500 } from "~/components/status-page";
|
import { Status500 } from "~/components/status-page";
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { handleDownload } from "~/hooks/filesHooks";
|
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import type { StorageindexId } from "~/schemas/public/Storageindex";
|
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
import { zAnnuncioId, zServizioId } from "~/server/utils/zod_types";
|
import { zAnnuncioId, zServizioId } from "~/server/utils/zod_types";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
@ -49,33 +47,23 @@ const ContrattoViewer: NextPageWithLayout<ContrattoViewerProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileSection = ({ storageId }: { storageId: StorageindexId }) => {
|
const FileSection = ({ storageId }: { storageId: string }) => {
|
||||||
const { data: fileInfos, isLoading } = api.storage.getStorageFromId.useQuery({
|
const { data: fileInfos, isLoading } = api.storage.getFileInfos.useQuery({
|
||||||
storageId,
|
storageId,
|
||||||
});
|
});
|
||||||
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
if (isLoading) return <LoadingPage />;
|
if (isLoading) return <LoadingPage />;
|
||||||
if (!fileInfos) return <p>{t.file_section.error}</p>;
|
if (!fileInfos) return <p>{t.file_section.error}</p>;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AllegatoIframe
|
<AllegatoIframe allegato={fileInfos} className="h-full max-h-[70vh]" />
|
||||||
allegato={fileInfos.id + fileInfos.ext}
|
|
||||||
className="h-full max-h-[70vh]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
<Link
|
||||||
className="underline underline-offset-1 after:content-['_↗']"
|
className="underline underline-offset-1 after:content-['_↗']"
|
||||||
onClick={() =>
|
href={`/storage-api/get/${fileInfos.id}?mode=download`}
|
||||||
handleDownload({
|
|
||||||
file: fileInfos,
|
|
||||||
getToken: async () => await getToken(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="link"
|
|
||||||
>
|
>
|
||||||
{t.file_section.download}
|
{t.file_section.download}
|
||||||
</Button>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
import type { GetServerSideProps } from "next";
|
import type { GetServerSideProps } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
import { AllegatoIframe } from "~/components/allegato-iframe";
|
import { AllegatoIframe } from "~/components/allegato-iframe";
|
||||||
import { DocNotFoundPage } from "~/components/doc_not_found";
|
import { DocNotFoundPage } from "~/components/doc_not_found";
|
||||||
import { AreaRiservataLayout } from "~/components/Layout";
|
import { AreaRiservataLayout } from "~/components/Layout";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import { Status500 } from "~/components/status-page";
|
import { Status500 } from "~/components/status-page";
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { handleDownload } from "~/hooks/filesHooks";
|
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import type { StorageindexId } from "~/schemas/public/Storageindex";
|
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
||||||
import { zAnnuncioId, zServizioId } from "~/server/utils/zod_types";
|
import { zAnnuncioId, zServizioId } from "~/server/utils/zod_types";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
@ -45,33 +43,23 @@ const ContrattoViewer: NextPageWithLayout<ContrattoViewerProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileSection = ({ storageId }: { storageId: StorageindexId }) => {
|
const FileSection = ({ storageId }: { storageId: string }) => {
|
||||||
const { data: fileInfos, isLoading } = api.storage.getStorageFromId.useQuery({
|
const { data: fileInfos, isLoading } = api.storage.getFileInfos.useQuery({
|
||||||
storageId,
|
storageId,
|
||||||
});
|
});
|
||||||
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
if (isLoading) return <LoadingPage />;
|
if (isLoading) return <LoadingPage />;
|
||||||
if (!fileInfos) return <p>{t.file_section.error}</p>;
|
if (!fileInfos) return <p>{t.file_section.error}</p>;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AllegatoIframe
|
<AllegatoIframe allegato={fileInfos} className="h-full max-h-[70vh]" />
|
||||||
allegato={fileInfos.id + fileInfos.ext}
|
|
||||||
className="h-full max-h-[70vh]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
<Link
|
||||||
className="underline underline-offset-1 after:content-['_↗']"
|
className="underline underline-offset-1 after:content-['_↗']"
|
||||||
onClick={() =>
|
href={`/storage-api/get/${fileInfos.id}?mode=download`}
|
||||||
handleDownload({
|
|
||||||
file: fileInfos,
|
|
||||||
getToken: async () => await getToken(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="link"
|
|
||||||
>
|
>
|
||||||
{t.file_section.download}
|
{t.file_section.download}
|
||||||
</Button>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -147,135 +147,6 @@ export default Test;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Test: NextPage = () => {
|
const Test: NextPage = () => {
|
||||||
return <FileManagementPage />;
|
return <div>Test page</div>;
|
||||||
};
|
};
|
||||||
export default Test;
|
export default Test;
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { LoadingPage } from "~/components/loading";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { Input } from "~/components/ui/input";
|
|
||||||
import {
|
|
||||||
deleteFile,
|
|
||||||
type FileMetadata,
|
|
||||||
fetchFiles,
|
|
||||||
uploadFile,
|
|
||||||
} from "~/server/storage";
|
|
||||||
|
|
||||||
function FileManagementPage() {
|
|
||||||
const [files, setFiles] = useState<FileMetadata[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
const loadData = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
const fileList = await fetchFiles();
|
|
||||||
setFiles(fileList);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [inputFile, setInputFile] = useState<File | null>(null);
|
|
||||||
const [inputExpiresAt, setInputExpiresAt] = useState<string | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleFileUpload = async () => {
|
|
||||||
if (inputFile) {
|
|
||||||
const success = await uploadFile(inputFile, inputExpiresAt);
|
|
||||||
if (success) {
|
|
||||||
// Reload list after successful upload
|
|
||||||
await loadData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async (fileID: string) => {
|
|
||||||
const confirmDelete = window.confirm("Are you sure?");
|
|
||||||
if (confirmDelete) {
|
|
||||||
const success = await deleteFile(fileID);
|
|
||||||
if (success) {
|
|
||||||
await loadData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-4 p-4">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<h1 className="font-bold text-2xl">File Storage</h1>
|
|
||||||
<Button
|
|
||||||
onClick={async () => {
|
|
||||||
await loadData();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
className=""
|
|
||||||
onChange={(e) => {
|
|
||||||
setInputFile(e.currentTarget.files?.[0] || null);
|
|
||||||
}}
|
|
||||||
type="file"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
className=""
|
|
||||||
onChange={(e) => {
|
|
||||||
setInputExpiresAt(new Date(e.currentTarget.value).toISOString());
|
|
||||||
}}
|
|
||||||
type="datetime-local"
|
|
||||||
/>
|
|
||||||
<Button onClick={handleFileUpload}>Upload</Button>
|
|
||||||
{loading ? (
|
|
||||||
<LoadingPage />
|
|
||||||
) : (
|
|
||||||
<ul className="space-y-2">
|
|
||||||
{files.map((file) => (
|
|
||||||
<li
|
|
||||||
className="flex items-center justify-between gap-3 rounded-lg border p-3"
|
|
||||||
key={file.id}
|
|
||||||
>
|
|
||||||
{file.mimeType.includes("image") && (
|
|
||||||
<img
|
|
||||||
alt={file.originalName}
|
|
||||||
className="size-20"
|
|
||||||
src={`/storage-api/file/${file.id}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className="font-semibold">{file.originalName}</p>
|
|
||||||
<p className="text-gray-500 text-sm">
|
|
||||||
{file.mimeType} | Size: {(file.size / 1024 / 1024).toFixed(2)}{" "}
|
|
||||||
MB
|
|
||||||
</p>
|
|
||||||
<p>Uploaded At: {new Date(file.uploadedAt).toLocaleString()}</p>
|
|
||||||
{file.expiresAt && (
|
|
||||||
<p>Expires At: {new Date(file.expiresAt).toLocaleString()}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<a
|
|
||||||
className="rounded bg-blue-500 px-3 py-1 text-sm text-white"
|
|
||||||
href={`/storage-api/file/${file.id}`}
|
|
||||||
>
|
|
||||||
Download
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
className="rounded bg-red-500 px-3 py-1 text-sm text-white"
|
|
||||||
onClick={() => handleDelete(file.id)}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,16 @@ import {
|
||||||
useContext,
|
useContext,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import type {
|
import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
|
||||||
Storageindex,
|
|
||||||
StorageindexId,
|
|
||||||
} from "~/schemas/public/Storageindex";
|
|
||||||
|
|
||||||
type TData = {
|
type TData = FileMetadataWithAdmin;
|
||||||
id: StorageindexId;
|
|
||||||
filename: string | null;
|
|
||||||
ext: string | null;
|
|
||||||
created_at: Date;
|
|
||||||
from_admin: boolean;
|
|
||||||
actions: string;
|
|
||||||
expires_at: Date | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type StorageTable = Table<TData>;
|
export type StorageTable = Table<TData>;
|
||||||
|
|
||||||
const StorageTableContext = createContext<{
|
const StorageTableContext = createContext<{
|
||||||
table: StorageTable | undefined;
|
table: StorageTable | undefined;
|
||||||
setTable: (table: StorageTable | undefined) => void;
|
setTable: (table: StorageTable | undefined) => void;
|
||||||
selectedRows: Storageindex[];
|
selectedRows: TData[];
|
||||||
cb_onStateChange: () => void;
|
cb_onStateChange: () => void;
|
||||||
}>({
|
}>({
|
||||||
cb_onStateChange: () => {},
|
cb_onStateChange: () => {},
|
||||||
|
|
@ -39,7 +28,7 @@ const StorageTableContext = createContext<{
|
||||||
export const StorageTableProvider = ({ children }: { children: ReactNode }) => {
|
export const StorageTableProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const [table, setTable] = useState<StorageTable | undefined>(undefined);
|
const [table, setTable] = useState<StorageTable | undefined>(undefined);
|
||||||
|
|
||||||
const [selectedRows, setSelectedRows] = useState<Storageindex[]>([]);
|
const [selectedRows, setSelectedRows] = useState<TData[]>([]);
|
||||||
|
|
||||||
const cb_onStateChange = useCallback(() => {
|
const cb_onStateChange = useCallback(() => {
|
||||||
if (table) {
|
if (table) {
|
||||||
|
|
@ -55,16 +44,7 @@ export const StorageTableProvider = ({ children }: { children: ReactNode }) => {
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
setSelectedRows(
|
setSelectedRows(selected);
|
||||||
selected.map((r) => ({
|
|
||||||
created_at: r.created_at,
|
|
||||||
expires_at: r.expires_at,
|
|
||||||
ext: r.ext,
|
|
||||||
filename: r.filename,
|
|
||||||
from_admin: r.from_admin,
|
|
||||||
id: r.id,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [table]);
|
}, [table]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import type { default as PrezziarioTable } from './Prezziario';
|
||||||
import type { default as RatelimiterTable } from './Ratelimiter';
|
import type { default as RatelimiterTable } from './Ratelimiter';
|
||||||
import type { default as ChatsTable } from './Chats';
|
import type { default as ChatsTable } from './Chats';
|
||||||
import type { default as EtichetteTable } from './Etichette';
|
import type { default as EtichetteTable } from './Etichette';
|
||||||
import type { default as StorageindexTable } from './Storageindex';
|
|
||||||
import type { default as MessagesTable } from './Messages';
|
import type { default as MessagesTable } from './Messages';
|
||||||
import type { default as UsersAnagraficaTable } from './UsersAnagrafica';
|
import type { default as UsersAnagraficaTable } from './UsersAnagrafica';
|
||||||
import type { default as BanlistTable } from './Banlist';
|
import type { default as BanlistTable } from './Banlist';
|
||||||
|
|
@ -63,8 +62,6 @@ export default interface PublicSchema {
|
||||||
|
|
||||||
etichette: EtichetteTable;
|
etichette: EtichetteTable;
|
||||||
|
|
||||||
storageindex: StorageindexTable;
|
|
||||||
|
|
||||||
messages: MessagesTable;
|
messages: MessagesTable;
|
||||||
|
|
||||||
users_anagrafica: UsersAnagraficaTable;
|
users_anagrafica: UsersAnagraficaTable;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import type { ServizioServizioId } from './Servizio';
|
import type { ServizioServizioId } from './Servizio';
|
||||||
import type { AnnunciId } from './Annunci';
|
import type { AnnunciId } from './Annunci';
|
||||||
import type { StorageindexId } from './Storageindex';
|
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Represents the table public.servizio_annunci */
|
/** Represents the table public.servizio_annunci */
|
||||||
|
|
@ -16,7 +15,7 @@ export default interface ServizioAnnunciTable {
|
||||||
|
|
||||||
open_contatti_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
open_contatti_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||||
|
|
||||||
doc_conferma_ref: ColumnType<StorageindexId | null, StorageindexId | null, StorageindexId | null>;
|
doc_conferma_ref: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
user_confirmed_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
user_confirmed_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||||
|
|
||||||
|
|
@ -32,7 +31,7 @@ export default interface ServizioAnnunciTable {
|
||||||
|
|
||||||
hasConfermaAdmin: ColumnType<boolean, boolean | undefined, boolean>;
|
hasConfermaAdmin: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
doc_contratto_ref: ColumnType<StorageindexId | null, StorageindexId | null, StorageindexId | null>;
|
doc_contratto_ref: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
gestionale_id: ColumnType<number | null, number | null, number | null>;
|
gestionale_id: ColumnType<number | null, number | null, number | null>;
|
||||||
|
|
||||||
|
|
@ -42,7 +41,7 @@ export default interface ServizioAnnunciTable {
|
||||||
|
|
||||||
contratto_tipo: ColumnType<string | null, string | null, string | null>;
|
contratto_tipo: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
doc_registrazione_ref: ColumnType<StorageindexId | null, StorageindexId | null, StorageindexId | null>;
|
doc_registrazione_ref: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
doc_conferma_added: ColumnType<boolean, boolean | undefined, boolean>;
|
doc_conferma_added: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// @generated
|
|
||||||
// This file is automatically generated by Kanel. Do not modify manually.
|
|
||||||
|
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
|
||||||
|
|
||||||
/** Identifier type for public.storageindex */
|
|
||||||
export type StorageindexId = string & { __brand: 'public.storageindex' };
|
|
||||||
|
|
||||||
/** Represents the table public.storageindex */
|
|
||||||
export default interface StorageindexTable {
|
|
||||||
id: ColumnType<StorageindexId, StorageindexId | undefined, StorageindexId>;
|
|
||||||
|
|
||||||
created_at: ColumnType<Date, Date | string | undefined, Date | string>;
|
|
||||||
|
|
||||||
filename: ColumnType<string | null, string | null, string | null>;
|
|
||||||
|
|
||||||
ext: ColumnType<string | null, string | null, string | null>;
|
|
||||||
|
|
||||||
from_admin: ColumnType<boolean, boolean | undefined, boolean>;
|
|
||||||
|
|
||||||
expires_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Storageindex = Selectable<StorageindexTable>;
|
|
||||||
|
|
||||||
export type NewStorageindex = Insertable<StorageindexTable>;
|
|
||||||
|
|
||||||
export type StorageindexUpdate = Updateable<StorageindexTable>;
|
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
// This file is automatically generated by Kanel. Do not modify manually.
|
// This file is automatically generated by Kanel. Do not modify manually.
|
||||||
|
|
||||||
import type { UsersId } from './Users';
|
import type { UsersId } from './Users';
|
||||||
import type { StorageindexId } from './Storageindex';
|
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Represents the table public.users_storage */
|
/** Represents the table public.users_storage */
|
||||||
export default interface UsersStorageTable {
|
export default interface UsersStorageTable {
|
||||||
userid: ColumnType<UsersId, UsersId, UsersId>;
|
userId: ColumnType<UsersId, UsersId, UsersId>;
|
||||||
|
|
||||||
storageid: ColumnType<StorageindexId, StorageindexId, StorageindexId>;
|
storageId: ColumnType<string, string, string>;
|
||||||
|
|
||||||
|
from_admin: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UsersStorage = Selectable<UsersStorageTable>;
|
export type UsersStorage = Selectable<UsersStorageTable>;
|
||||||
|
|
|
||||||
|
|
@ -1,151 +1,90 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod/v4";
|
import z from "zod";
|
||||||
|
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
protectedProcedure,
|
protectedProcedure,
|
||||||
} from "~/server/api/trpc";
|
} from "~/server/api/trpc";
|
||||||
import {
|
import {
|
||||||
addUserStorageHandler,
|
addUserStorage,
|
||||||
deleteUserStorageHandler,
|
deleteUserStorageEntry,
|
||||||
getAllHandler,
|
getAllWithFromAdmin,
|
||||||
getFromIdHandler,
|
|
||||||
getMultipleWithRelations,
|
getMultipleWithRelations,
|
||||||
getUserStorageHandler,
|
purgeFileFromUserStorages,
|
||||||
|
retrieveUserFiles,
|
||||||
} from "~/server/controllers/storage.controller";
|
} from "~/server/controllers/storage.controller";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { RateLimiterHandler } from "~/server/services/ratelimiter";
|
|
||||||
import {
|
import {
|
||||||
addUserStorageJunc,
|
deleteFile,
|
||||||
deleteHandler,
|
editFile,
|
||||||
deleteUserStorageJunc,
|
getFileInfo,
|
||||||
getNewTokenHandler,
|
|
||||||
updateHandler,
|
|
||||||
} from "~/server/services/storage.service";
|
} from "~/server/services/storage.service";
|
||||||
import { zStorageIndexId, zUserId } from "~/server/utils/zod_types";
|
import { zUserId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
const ratelimit = new RateLimiterHandler({
|
|
||||||
analytics: false,
|
|
||||||
maxRequests: 50,
|
|
||||||
windowSize: 60,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const storageRouter = createTRPCRouter({
|
export const storageRouter = createTRPCRouter({
|
||||||
addStorageVisibility: adminProcedure
|
getFileInfos: protectedProcedure
|
||||||
.input(z.object({ storageId: zStorageIndexId, userId: zUserId }))
|
.input(z.object({ storageId: z.string() }))
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
await addUserStorageJunc({
|
|
||||||
db,
|
|
||||||
storageId: input.storageId,
|
|
||||||
userId: input.userId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
addUserStorage: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
fileId: zStorageIndexId,
|
|
||||||
userId: zUserId,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await addUserStorageHandler({
|
|
||||||
db,
|
|
||||||
storageId: input.fileId,
|
|
||||||
userId: input.userId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
deleteStorage: protectedProcedure
|
|
||||||
.input(z.object({ storageId: zStorageIndexId }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await deleteHandler({
|
|
||||||
cb: async (fileId) => {
|
|
||||||
await db
|
|
||||||
.deleteFrom("storageindex")
|
|
||||||
.where("id", "=", fileId)
|
|
||||||
.execute();
|
|
||||||
},
|
|
||||||
db,
|
|
||||||
fileId: input.storageId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
deleteUserStorage: protectedProcedure
|
|
||||||
.input(z.object({ fileId: zStorageIndexId, userId: zUserId }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await deleteUserStorageHandler({
|
|
||||||
db,
|
|
||||||
storageId: input.fileId,
|
|
||||||
userId: input.userId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
getAll: protectedProcedure.query(async () => {
|
|
||||||
return await getAllHandler({ db });
|
|
||||||
}),
|
|
||||||
getStorageFromId: protectedProcedure
|
|
||||||
.input(z.object({ storageId: zStorageIndexId }))
|
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getFromIdHandler({
|
return (await getFileInfo(input.storageId)) || undefined;
|
||||||
db,
|
|
||||||
storage_id: input.storageId,
|
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
getStorageToken: protectedProcedure.mutation(async () => {
|
deleteFile: protectedProcedure
|
||||||
const { success } = await ratelimit.limit("getStorageToken");
|
.input(z.object({ storageId: z.string() }))
|
||||||
if (!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" });
|
.mutation(async ({ input }) => {
|
||||||
const token = (await getNewTokenHandler({ db }))[0];
|
const deletion = await deleteFile(input.storageId);
|
||||||
if (!token) {
|
if (!deletion.success) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "Error while creating token",
|
message: deletion.error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return token;
|
await purgeFileFromUserStorages({
|
||||||
|
...input,
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
getStorageTokenM: protectedProcedure
|
deleteUserStorageEntry: protectedProcedure
|
||||||
.input(z.object({ num: z.number() }))
|
.input(z.object({ storageId: z.string(), userId: zUserId }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const { success } = await ratelimit.limit("getStorageToken");
|
return await deleteUserStorageEntry(input);
|
||||||
if (!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" });
|
|
||||||
return await getNewTokenHandler({ db, num: input.num });
|
|
||||||
}),
|
}),
|
||||||
|
deleteFilesFromUserStorage: adminProcedure
|
||||||
|
.input(z.object({ storageIds: z.string().array() }))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
for (const storageId of input.storageIds) {
|
||||||
|
await purgeFileFromUserStorages({ storageId });
|
||||||
|
}
|
||||||
|
return "ok";
|
||||||
|
}),
|
||||||
|
retrieveUserFileData: protectedProcedure
|
||||||
|
.input(z.object({ userId: zUserId }))
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
return await retrieveUserFiles(input);
|
||||||
|
}),
|
||||||
|
|
||||||
|
//OLD
|
||||||
|
|
||||||
|
addUserStorage: protectedProcedure
|
||||||
|
.input(z.custom<NewUsersStorage>())
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
return await addUserStorage(input);
|
||||||
|
}),
|
||||||
|
|
||||||
|
getAll: protectedProcedure.query(async () => {
|
||||||
|
return await getAllWithFromAdmin();
|
||||||
|
}),
|
||||||
|
|
||||||
getStorageVisibility: adminProcedure
|
getStorageVisibility: adminProcedure
|
||||||
.input(z.object({ storageIds: zStorageIndexId.array() }))
|
.input(z.object({ storageIds: z.string().array() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getMultipleWithRelations({
|
return await getMultipleWithRelations({
|
||||||
db,
|
db,
|
||||||
ids: input.storageIds,
|
ids: input.storageIds,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
getUserStorage: protectedProcedure
|
|
||||||
.input(z.object({ userId: zUserId }))
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getUserStorageHandler({ db, userId: input.userId });
|
|
||||||
}),
|
|
||||||
removeStorageVisibility: adminProcedure
|
|
||||||
.input(z.object({ storageId: zStorageIndexId, userId: zUserId }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
await deleteUserStorageJunc({
|
|
||||||
db,
|
|
||||||
storageId: input.storageId,
|
|
||||||
userId: input.userId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
renameFile: adminProcedure
|
renameFile: adminProcedure
|
||||||
.input(z.object({ fileId: zStorageIndexId, name: z.string() }))
|
.input(z.object({ fileId: z.string(), name: z.string() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await updateHandler({
|
return await editFile(input.fileId, input.name);
|
||||||
data: { filename: input.name },
|
|
||||||
db,
|
|
||||||
fileId: input.fileId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
resetStorageVisibility: adminProcedure
|
|
||||||
.input(z.object({ storageId: zStorageIndexId.array() }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
await db
|
|
||||||
.deleteFrom("users_storage")
|
|
||||||
.where("storageid", "in", input.storageId)
|
|
||||||
.execute();
|
|
||||||
return "ok";
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,121 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { jsonArrayFrom } from "kysely/helpers/postgres";
|
|
||||||
import type {
|
|
||||||
Storageindex,
|
|
||||||
StorageindexId,
|
|
||||||
} from "~/schemas/public/Storageindex";
|
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { Querier } from "~/server/db";
|
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
||||||
import {
|
import { db, type Querier } from "~/server/db";
|
||||||
addUserStorageJunc,
|
import { fetchFiles, retriveFilesSafely } from "../services/storage.service";
|
||||||
deleteHandler,
|
|
||||||
deleteUserStorageJunc,
|
|
||||||
} from "~/server/services/storage.service";
|
|
||||||
|
|
||||||
export const getAllHandler = async ({ db }: { db: Querier }) => {
|
export const getMultipleWithRelations = async ({
|
||||||
return await db.selectFrom("storageindex").selectAll().execute();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFromIdHandler = async ({
|
|
||||||
db,
|
db,
|
||||||
storage_id,
|
ids,
|
||||||
}: {
|
}: {
|
||||||
db: Querier;
|
db: Querier;
|
||||||
storage_id: StorageindexId;
|
ids: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return await db
|
const files = await fetchFiles();
|
||||||
.selectFrom("storageindex")
|
const userStorage = await db
|
||||||
.selectAll()
|
.selectFrom("users_storage")
|
||||||
.where("id", "=", storage_id)
|
.selectAll("users_storage")
|
||||||
.executeTakeFirst();
|
.innerJoin("users", "users.id", "users_storage.userId")
|
||||||
|
.select("users.username")
|
||||||
|
.where(
|
||||||
|
"storageId",
|
||||||
|
"in",
|
||||||
|
files.map((f) => f.id),
|
||||||
|
)
|
||||||
|
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return ids
|
||||||
|
.map((id) => {
|
||||||
|
const file = files.find((f) => f.id === id);
|
||||||
|
if (!file) return null;
|
||||||
|
const users = userStorage
|
||||||
|
.filter((us) => us.storageId === id)
|
||||||
|
.map((us) => ({
|
||||||
|
id: us.userId,
|
||||||
|
username: us.username,
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
users,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((f) => f !== null);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserStorageHandler = async ({
|
export const retrieveUserFiles = async ({ userId }: { userId: UsersId }) => {
|
||||||
db,
|
|
||||||
userId,
|
|
||||||
}: {
|
|
||||||
db: Querier;
|
|
||||||
userId: UsersId;
|
|
||||||
}): Promise<Storageindex[]> => {
|
|
||||||
try {
|
try {
|
||||||
return await db
|
//all files linked to the user
|
||||||
.selectFrom("storageindex")
|
const files = await db
|
||||||
.selectAll("storageindex")
|
.selectFrom("users_storage")
|
||||||
.rightJoin("users_storage", "users_storage.storageid", "storageindex.id")
|
.select(["storageId", "from_admin"])
|
||||||
.where("users_storage.userid", "=", userId)
|
.where("userId", "=", userId)
|
||||||
.orderBy("created_at", "desc")
|
|
||||||
.execute();
|
.execute();
|
||||||
} catch {
|
const { available, unavailable } = await retriveFilesSafely(
|
||||||
|
files.map((f) => f.storageId),
|
||||||
|
);
|
||||||
|
|
||||||
|
//remove entries in users_storage for files that no longer exist in storage
|
||||||
|
await db
|
||||||
|
.deleteFrom("users_storage")
|
||||||
|
.where("userId", "=", userId)
|
||||||
|
.where("storageId", "in", unavailable)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
//merge file metadata with users_storage info
|
||||||
|
const userFiles = files
|
||||||
|
.map((uf) => {
|
||||||
|
const file = available.find((f) => f.id === uf.storageId);
|
||||||
|
if (!file) return null;
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
from_admin: uf.from_admin,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((f) => f !== null);
|
||||||
|
|
||||||
|
return userFiles;
|
||||||
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "Error while getting user storage",
|
message: `Error while getting user storage: ${(e as Error).message}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addUserStorageHandler = async ({
|
export const getAllWithFromAdmin = async () => {
|
||||||
db,
|
|
||||||
userId,
|
|
||||||
storageId,
|
|
||||||
}: {
|
|
||||||
db: Querier;
|
|
||||||
userId: UsersId;
|
|
||||||
storageId: StorageindexId;
|
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
await addUserStorageJunc({
|
const files = await db
|
||||||
db,
|
.selectFrom("users_storage")
|
||||||
storageId,
|
.select(["storageId", "from_admin"])
|
||||||
userId,
|
.execute();
|
||||||
|
|
||||||
|
const allFiles = await fetchFiles();
|
||||||
|
const allFilesWithFromAdmin = allFiles.map((file) => {
|
||||||
|
const userStorageEntry = files.find((f) => f.storageId === file.id);
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
from_admin: userStorageEntry ? userStorageEntry.from_admin : true,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return "ok";
|
return allFilesWithFromAdmin;
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Error while getting user storage: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addUserStorage = async (data: NewUsersStorage) => {
|
||||||
|
try {
|
||||||
|
return await db
|
||||||
|
.insertInto("users_storage")
|
||||||
|
.values(data)
|
||||||
|
.onConflict((oc) => oc.columns(["userId", "storageId"]).doNothing())
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
@ -78,114 +124,58 @@ export const addUserStorageHandler = async ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteUserStorageHandler = async ({
|
export const getUserStorage = async ({ userId }: { userId: UsersId }) => {
|
||||||
db,
|
try {
|
||||||
|
return await db
|
||||||
|
.selectFrom("users_storage")
|
||||||
|
.where("userId", "=", userId)
|
||||||
|
.selectAll()
|
||||||
|
.execute();
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Error while getting user storage: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteUserStorageEntry = async ({
|
||||||
userId,
|
userId,
|
||||||
storageId,
|
storageId,
|
||||||
}: {
|
}: {
|
||||||
db: Querier;
|
|
||||||
userId: UsersId;
|
userId: UsersId;
|
||||||
storageId: StorageindexId;
|
storageId: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const file = await db
|
await db
|
||||||
.selectFrom("users_storage")
|
.deleteFrom("users_storage")
|
||||||
.selectAll()
|
.where("userId", "=", userId)
|
||||||
.where("userid", "=", userId)
|
.where("storageId", "=", storageId)
|
||||||
.where("storageid", "=", storageId)
|
|
||||||
.executeTakeFirst();
|
|
||||||
if (!file) {
|
|
||||||
return "no file";
|
|
||||||
}
|
|
||||||
|
|
||||||
await deleteHandler({
|
|
||||||
cb: async (fileId) => {
|
|
||||||
await db.transaction().execute(async (trx) => {
|
|
||||||
await deleteUserStorageJunc({
|
|
||||||
db: trx,
|
|
||||||
storageId: fileId,
|
|
||||||
userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
await trx
|
|
||||||
.deleteFrom("storageindex")
|
|
||||||
.where("id", "=", fileId)
|
|
||||||
.execute();
|
.execute();
|
||||||
});
|
|
||||||
},
|
|
||||||
db,
|
|
||||||
fileId: file.storageid,
|
|
||||||
});
|
|
||||||
return "ok";
|
return "ok";
|
||||||
} catch {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "Error while deleting user storage",
|
message: `Error while deleting user storage: ${(e as Error).message}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const deleteAllUserStorageHandler = async ({
|
export const purgeFileFromUserStorages = async ({
|
||||||
// db,
|
storageId,
|
||||||
// userId,
|
|
||||||
// }: {
|
|
||||||
// db: Querier;
|
|
||||||
// userId: UsersId;
|
|
||||||
// }) => {
|
|
||||||
// try {
|
|
||||||
// const files = await db
|
|
||||||
// .selectFrom("users_storage")
|
|
||||||
// .select("storageid")
|
|
||||||
// .where("userid", "=", userId)
|
|
||||||
// .execute();
|
|
||||||
|
|
||||||
// if (!files || files.length === 0) {
|
|
||||||
// return "no files";
|
|
||||||
// }
|
|
||||||
// await deleteFiles({
|
|
||||||
// db,
|
|
||||||
// refs: files,
|
|
||||||
// cb: async (fileIds) => {
|
|
||||||
// const ids = fileIds.map((file) => file.storageid);
|
|
||||||
// await db.transaction().execute(async (trx) => {
|
|
||||||
// await trx
|
|
||||||
// .deleteFrom("users_storage")
|
|
||||||
// .where("userid", "=", userId)
|
|
||||||
// .where("storageid", "in", ids)
|
|
||||||
// .execute();
|
|
||||||
// await trx.deleteFrom("storageindex").where("id", "in", ids).execute();
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// return "ok";
|
|
||||||
// } catch {
|
|
||||||
// throw new TRPCError({
|
|
||||||
// code: "INTERNAL_SERVER_ERROR",
|
|
||||||
// message: "Error while deleting user storage",
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const getMultipleWithRelations = async ({
|
|
||||||
db,
|
|
||||||
ids,
|
|
||||||
}: {
|
}: {
|
||||||
db: Querier;
|
storageId: string;
|
||||||
ids: StorageindexId[];
|
|
||||||
}) => {
|
}) => {
|
||||||
return await db
|
try {
|
||||||
.selectFrom("storageindex")
|
await db
|
||||||
.selectAll("storageindex")
|
.deleteFrom("users_storage")
|
||||||
.select((eb) => [
|
.where("storageId", "=", storageId)
|
||||||
jsonArrayFrom(
|
|
||||||
eb
|
|
||||||
.selectFrom("users_storage")
|
|
||||||
.leftJoin("users", "users.id", "users_storage.userid")
|
|
||||||
.select(["users.username"])
|
|
||||||
.whereRef("users_storage.storageid", "=", "storageindex.id")
|
|
||||||
.selectAll("users_storage"),
|
|
||||||
).as("users_storages"),
|
|
||||||
])
|
|
||||||
.where("id", "in", ids)
|
|
||||||
.execute();
|
.execute();
|
||||||
|
return "ok";
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Error while deleting user storage: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,146 +1,210 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import z from "zod";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import type {
|
|
||||||
StorageindexId,
|
|
||||||
StorageindexUpdate,
|
|
||||||
} from "~/schemas/public/Storageindex";
|
|
||||||
import type { TempTokensToken } from "~/schemas/public/TempTokens";
|
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
|
||||||
import type { Querier } from "~/server/db";
|
|
||||||
import { deleteFile } from "../storage";
|
|
||||||
|
|
||||||
export const getNewTokenHandler = async ({
|
const FileMetadataSchema = z.object({
|
||||||
db,
|
id: z.string(),
|
||||||
num,
|
originalName: z.string(),
|
||||||
}: {
|
size: z.number(),
|
||||||
db: Querier;
|
mimeType: z.string(),
|
||||||
num?: number;
|
blockCount: z.number(),
|
||||||
}): Promise<TempTokensToken[]> => {
|
uploadedAt: z.string(),
|
||||||
const tokens: TempTokensToken[] = [];
|
expiresAt: z.string().nullable(),
|
||||||
if (num && num > 1) {
|
});
|
||||||
for (let i = 0; i < num; i++) {
|
|
||||||
const tok = await newToken({ db });
|
|
||||||
|
|
||||||
tokens.push(tok.token);
|
export type FileMetadata = z.infer<typeof FileMetadataSchema>;
|
||||||
}
|
|
||||||
return tokens;
|
export type FileMetadataWithAdmin = FileMetadata & {
|
||||||
}
|
from_admin: boolean;
|
||||||
const tok = await newToken({ db });
|
|
||||||
return [tok.token];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const newToken = async ({ db }: { db: Querier }) => {
|
// Helper to fetch the list of all files
|
||||||
|
export async function fetchFiles(): Promise<FileMetadata[]> {
|
||||||
try {
|
try {
|
||||||
return await db
|
const response = await fetch(
|
||||||
.insertInto("temp_tokens")
|
`${env.STORAGE_URL}/files?token=${env.STORAGE_TOKEN}`,
|
||||||
.defaultValues()
|
);
|
||||||
.returning("token")
|
if (!response.ok) {
|
||||||
.executeTakeFirstOrThrow();
|
console.error("Failed to fetch file list:", response.statusText);
|
||||||
} catch {
|
return [];
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: "Error while creating token",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
const parse = FileMetadataSchema.array().safeParse(await response.json());
|
||||||
|
|
||||||
export const deleteHandler = async ({
|
if (parse.success) {
|
||||||
db,
|
return parse.data;
|
||||||
fileId,
|
|
||||||
cb,
|
|
||||||
}: {
|
|
||||||
db: Querier;
|
|
||||||
fileId: StorageindexId;
|
|
||||||
cb?: (fileId: StorageindexId) => Promise<void>;
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
const storage = await db
|
|
||||||
.selectFrom("storageindex")
|
|
||||||
.select(["id", "ext"])
|
|
||||||
.where("id", "=", fileId)
|
|
||||||
.executeTakeFirstOrThrow();
|
|
||||||
await deleteFile(storage.id);
|
|
||||||
|
|
||||||
if (cb) {
|
|
||||||
await cb(fileId);
|
|
||||||
}
|
}
|
||||||
|
console.error("Failed to parse file metadata:", parse.error);
|
||||||
|
return [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error:", error);
|
console.error("Error fetching file list:", error);
|
||||||
throw new TRPCError({
|
return [];
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: "Error while deleting file",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const updateHandler = async ({
|
export async function getFileInfo(id: string): Promise<FileMetadata | null> {
|
||||||
db,
|
|
||||||
fileId,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
db: Querier;
|
|
||||||
fileId: StorageindexId;
|
|
||||||
data: StorageindexUpdate;
|
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
await db
|
const response = await fetch(
|
||||||
.updateTable("storageindex")
|
`${env.STORAGE_URL}/info/${id}?token=${env.STORAGE_TOKEN}`,
|
||||||
.set(data)
|
);
|
||||||
.where("id", "=", fileId)
|
if (!response.ok) {
|
||||||
.execute();
|
console.error("Failed to fetch file list:", response.statusText);
|
||||||
return "ok";
|
return null;
|
||||||
} catch (e) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: `Error while updating file: ${(e as Error).message}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
const parse = FileMetadataSchema.safeParse(await response.json());
|
||||||
|
|
||||||
export const addUserStorageJunc = async ({
|
if (parse.success) {
|
||||||
db,
|
return parse.data;
|
||||||
userId,
|
|
||||||
storageId,
|
|
||||||
}: {
|
|
||||||
db: Querier;
|
|
||||||
userId: UsersId;
|
|
||||||
storageId: StorageindexId;
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
return await db
|
|
||||||
.insertInto("users_storage")
|
|
||||||
.values({ storageid: storageId, userid: userId })
|
|
||||||
.onConflict((oc) => oc.columns(["userid", "storageid"]).doNothing())
|
|
||||||
.returningAll()
|
|
||||||
.execute();
|
|
||||||
} catch (e) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: `Error while adding user storage: ${(e as Error).message}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
console.error("Failed to parse file metadata:", parse.error);
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching file list:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const deleteUserStorageJunc = async ({
|
type EditResponse =
|
||||||
db,
|
| {
|
||||||
userId,
|
success: true;
|
||||||
storageId,
|
id: string;
|
||||||
}: {
|
|
||||||
db: Querier;
|
|
||||||
userId: UsersId;
|
|
||||||
storageId: StorageindexId;
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
await db
|
|
||||||
.deleteFrom("users_storage")
|
|
||||||
.where("userid", "=", userId)
|
|
||||||
.where("storageid", "=", storageId)
|
|
||||||
.execute();
|
|
||||||
return "ok";
|
|
||||||
} catch (e) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: `Error while deleting user storage: ${(e as Error).message}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
| {
|
||||||
|
success: false;
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
export async function editFile(
|
||||||
|
id: string,
|
||||||
|
filename?: string,
|
||||||
|
expiresAt?: string,
|
||||||
|
): Promise<EditResponse> {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("id", id);
|
||||||
|
if (expiresAt) {
|
||||||
|
formData.append("expires_at", expiresAt);
|
||||||
|
}
|
||||||
|
if (filename) {
|
||||||
|
formData.append("filename", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${env.STORAGE_URL}/edit?token=${env.STORAGE_TOKEN}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const res = await response.json();
|
||||||
|
const parsed = z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
})
|
||||||
|
.parse(res);
|
||||||
|
const fileID = parsed.id;
|
||||||
|
console.log("File edited successfully.");
|
||||||
|
return { success: true, id: fileID };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Modifica fallita con status: ${response.status}`,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Errore Modifica file: ${(e as Error).message}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteResponse =
|
||||||
|
| {
|
||||||
|
success: true;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
// Helper to handle file deletion
|
||||||
|
export async function deleteFile(fileID: string): Promise<DeleteResponse> {
|
||||||
|
try {
|
||||||
|
// The Go server accepts POST with form data for delete
|
||||||
|
const response = await fetch(
|
||||||
|
`${env.STORAGE_URL}/delete/${fileID}?token=${env.STORAGE_TOKEN}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Treat redirect as success
|
||||||
|
if (response.ok || (response.status >= 300 && response.status < 400)) {
|
||||||
|
console.log(`File ${fileID} deleted successfully.`);
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `Eliminazione fallita con status: ${response.status}`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during file deletion:", error);
|
||||||
|
return { success: false, error: (error as Error).message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// export const handleDownload = async (file: FileMetadata) => {
|
||||||
|
// try {
|
||||||
|
// const response = await fetch(
|
||||||
|
// `${env.STORAGE_URL}/get/${file.id}?token=${env.STORAGE_TOKEN}&mode=download`,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (!response.ok) {
|
||||||
|
// throw new Error("File fetch failed");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const blob = await response.blob();
|
||||||
|
// const url = window.URL.createObjectURL(blob);
|
||||||
|
// const a = document.createElement("a");
|
||||||
|
// a.href = url;
|
||||||
|
// a.download = file.originalName;
|
||||||
|
// document.body.appendChild(a);
|
||||||
|
// a.click();
|
||||||
|
// a.remove();
|
||||||
|
// window.URL.revokeObjectURL(url);
|
||||||
|
// } catch {
|
||||||
|
// throw new Error("File download failed");
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const safeRetrive = z.object({
|
||||||
|
available: z.array(FileMetadataSchema),
|
||||||
|
unavailable: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function retriveFilesSafely(ids: string[]) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${env.STORAGE_URL}/files-check?token=${env.STORAGE_TOKEN}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(ids),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("File fetch failed");
|
||||||
|
}
|
||||||
|
const parse = safeRetrive.safeParse(await response.json());
|
||||||
|
|
||||||
|
if (parse.success) {
|
||||||
|
return parse.data;
|
||||||
|
}
|
||||||
|
throw new Error("File fetch parse failed");
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`File fetch failed: ${(e as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
import z from "zod";
|
|
||||||
|
|
||||||
const FileMetadataSchema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
originalName: z.string(),
|
|
||||||
size: z.number(),
|
|
||||||
mimeType: z.string(),
|
|
||||||
blockCount: z.number(),
|
|
||||||
uploadedAt: z.string(),
|
|
||||||
expiresAt: z.string().nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type FileMetadata = z.infer<typeof FileMetadataSchema>;
|
|
||||||
|
|
||||||
// Helper to fetch the list of all files
|
|
||||||
export async function fetchFiles(): Promise<FileMetadata[]> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/storage-api/api/files`);
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error("Failed to fetch file list:", response.statusText);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const parse = FileMetadataSchema.array().safeParse(await response.json());
|
|
||||||
|
|
||||||
if (parse.success) {
|
|
||||||
return parse.data;
|
|
||||||
}
|
|
||||||
console.error("Failed to parse file metadata:", parse.error);
|
|
||||||
return [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching file list:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to handle file uploads
|
|
||||||
export async function uploadFile(
|
|
||||||
file: File,
|
|
||||||
expiresAt: string | undefined,
|
|
||||||
): Promise<boolean> {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", file);
|
|
||||||
if (expiresAt) {
|
|
||||||
formData.append("expires_at", expiresAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/storage-api/upload`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
// The Go server redirects on success (StatusSeeOther), which may cause CORS issues
|
|
||||||
// or be difficult to read in a browser environment.
|
|
||||||
// We'll treat any successful status (2xx or 3xx) as a success for now.
|
|
||||||
if (response.ok || (response.status >= 300 && response.status < 400)) {
|
|
||||||
console.log("File uploaded successfully.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Upload failed with status: ${response.status}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error during file upload:", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to delete a file
|
|
||||||
export async function deleteFile(fileID: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
// The Go server accepts POST with form data for delete
|
|
||||||
const response = await fetch(`/storage-api/delete/${fileID}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Treat redirect as success
|
|
||||||
if (response.ok || (response.status >= 300 && response.status < 400)) {
|
|
||||||
console.log(`File ${fileID} deleted successfully.`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Delete failed with status: ${response.status}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error during file deletion:", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,7 +10,6 @@ import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import type { PaymentsId } from "~/schemas/public/Payments";
|
import type { PaymentsId } from "~/schemas/public/Payments";
|
||||||
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
|
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import type { StorageindexId } from "~/schemas/public/Storageindex";
|
|
||||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { EventDataTypes } from "~/server/controllers/event_queue.controller";
|
import type { EventDataTypes } from "~/server/controllers/event_queue.controller";
|
||||||
|
|
@ -45,11 +44,6 @@ export const zFlagsId = z.custom<FlagsId>(
|
||||||
"falied to validate FlagsId",
|
"falied to validate FlagsId",
|
||||||
);
|
);
|
||||||
|
|
||||||
export const zStorageIndexId = z.custom<StorageindexId>(
|
|
||||||
(val) => typeof val === "string",
|
|
||||||
"falied to validate StorageIndexId",
|
|
||||||
);
|
|
||||||
|
|
||||||
export const zEtichettaId = z.custom<EtichetteIdEtichetta>(
|
export const zEtichettaId = z.custom<EtichetteIdEtichetta>(
|
||||||
(val) => typeof val === "number",
|
(val) => typeof val === "number",
|
||||||
"falied to validate EtichettaId",
|
"falied to validate EtichettaId",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue