refactor: update storage-related API and components for improved functionality

This commit is contained in:
Marco Pedone 2025-10-24 18:05:51 +02:00
parent 7b619e54db
commit 7f93bdeb98
10 changed files with 165 additions and 102 deletions

View file

@ -65,9 +65,10 @@ ENV TILES_URL="localhost:6379"
ENV SKEBBY_USER="mock"
ENV SKEBBY_PASS="mock"
ENV REVALIDATION_SECRET="mock"
ENV STORAGE_URL="mock"
ENV STORAGE_TOKEN="mock"
#TODO remove variabili non necessarie al build tanto la validazione viene saltata
RUN SKIP_ENV_VALIDATION=1 npm run build
@ -132,6 +133,8 @@ ENV SKEBBY_USER=$SKEBBY_USER
ARG SKEBBY_PASS
ENV SKEBBY_PASS=$SKEBBY_PASS
ENV REVALIDATION_SECRET="SWKpgaaLfsyeqV1eOT0WG7TUFBewir8kJXure3O37ki8Lt4Z4IiEgBW4zPHDdM5c"
ARG STORAGE_URL
ENV STORAGE_URL=$STORAGE_URL
ARG STORAGE_TOKEN
ENV STORAGE_TOKEN=$STORAGE_TOKEN

View file

@ -59,10 +59,6 @@ async function createNextConfig(): Promise<NextConfig> {
reactStrictMode: true,
rewrites: async () => {
return [
{
source: "/storage-api/:slug*",
destination: `http://localhost:8080/:slug*?token=${env.STORAGE_TOKEN}`,
},
{
destination: env.NODE_ENV === "production" ? "/404" : "/api/panel",
source: "/api/panel",
@ -71,32 +67,6 @@ async function createNextConfig(): Promise<NextConfig> {
destination: `${env.BACKENDSERVER_URL}/images/get/:slug*`,
source: "/go-api/images/get/:slug*",
},
{
destination: `${env.BACKENDSERVER_URL}/storage/get/:slug*`,
has: [
{ key: "access_token", type: "cookie" },
{
key: "token",
type: "query",
value:
"(?<token>^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$)",
},
],
source: "/go-api/storage/get/:slug*",
},
{
destination: `${env.BACKENDSERVER_URL}/storage/upload:slug*`,
has: [
{ key: "access_token", type: "cookie" },
{
key: "token",
type: "query",
value:
"(?<token>^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$)",
},
],
source: "/go-api/storage/upload:slug*",
},
];
},

View file

@ -500,14 +500,15 @@ const ContrattoSection = ({
const { data: files, isLoading } = api.storage.getAll.useQuery();
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
onSuccess: async () => {
await utils.storage.retrieveUserFileData.invalidate({
userId,
});
await utils.storage.getAll.invalidate();
},
});
const { mutate: setUserStorage } =
api.storage.addUserStorageEntry.useMutation({
onSuccess: async () => {
await utils.storage.retrieveUserFileData.invalidate({
userId,
});
await utils.storage.getAll.invalidate();
},
});
const handleSave = () => {
handleUpdate({
@ -592,14 +593,15 @@ const RicevutaSection = ({
const { data: files, isLoading } = api.storage.getAll.useQuery();
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
onSuccess: async () => {
await utils.storage.retrieveUserFileData.invalidate({
userId,
});
await utils.storage.getAll.invalidate();
},
});
const { mutate: setUserStorage } =
api.storage.addUserStorageEntry.useMutation({
onSuccess: async () => {
await utils.storage.retrieveUserFileData.invalidate({
userId,
});
await utils.storage.getAll.invalidate();
},
});
const handleSave = () => {
handleUpdate({
@ -757,14 +759,15 @@ const DocSection = ({
const { data: files, isLoading } = api.storage.getAll.useQuery();
const { mutate: setUserStorage } = api.storage.addUserStorage.useMutation({
onSuccess: async () => {
await utils.storage.retrieveUserFileData.invalidate({
userId,
});
await utils.storage.getAll.invalidate();
},
});
const { mutate: setUserStorage } =
api.storage.addUserStorageEntry.useMutation({
onSuccess: async () => {
await utils.storage.retrieveUserFileData.invalidate({
userId,
});
await utils.storage.getAll.invalidate();
},
});
if (isLoading) return <LoadingPage />;
if (files === undefined) {

View file

@ -36,13 +36,8 @@ import {
} from "~/components/ui/dropdown-menu";
import { Input } from "~/components/ui/input";
import { cn } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider";
import { useStorageTable } from "~/providers/StorageTableProvider";
import {
editFile,
type FileMetadataWithAdmin,
} from "~/server/services/storage.service";
import type { FileMetadataWithAdmin } from "~/server/services/storage.service";
import { api } from "~/utils/api";
export const StorageTable = ({
@ -283,9 +278,24 @@ const RinominaDialog = ({
target: { allegatoId: string; filename: string | null } | null,
) => void;
}) => {
const { t } = useTranslation();
const utils = api.useUtils();
const { mutateAsync: renameFile } = api.storage.renameFile.useMutation({
onMutate: () => {
const toastId = toast.loading("Modifica in corso", {
icon: "🪛",
});
return { toastId };
},
onSuccess: async (_error, _variables, context) => {
await utils.storage.getAll.invalidate();
toast.success("File rinominato con successo", {
icon: "👍",
id: context?.toastId,
});
},
});
const handleEditSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = e.currentTarget;
@ -293,17 +303,10 @@ const RinominaDialog = ({
if (!formData.get("filename")) {
return;
}
const rename = await editFile(
renameTarget.allegatoId,
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);
}
await renameFile({
fileId: renameTarget.allegatoId,
name: formData.get("filename") as string,
});
};
return (
<Dialog
@ -312,8 +315,8 @@ const RinominaDialog = ({
>
<DialogContent>
<DialogHeader>
<DialogTitle>{t.modifica}</DialogTitle>
<DialogDescription className="sr-only">modifica</DialogDescription>
<DialogTitle>Rinomina</DialogTitle>
<DialogDescription className="sr-only">Rinomina</DialogDescription>
</DialogHeader>
<form method="post" onSubmit={handleEditSubmit}>
@ -323,10 +326,10 @@ const RinominaDialog = ({
defaultValue={renameTarget.filename || undefined}
minLength={5}
name="filename"
placeholder="Modifica messaggio"
placeholder="Rinomina File"
type="text"
/>
<Button type="submit">{t.salva}</Button>
<Button type="submit">Salva</Button>
</div>
</form>
</DialogContent>

View file

@ -88,7 +88,7 @@ export const UploadComponent = ({
const [isUploading, setIsUploading] = useState(false);
const { mutateAsync: setUserStorage } =
api.storage.addUserStorage.useMutation({
api.storage.addUserStorageEntry.useMutation({
onMutate: () => {
toast(t.allegati.allegato_caricato, { icon: "📤" });
},

View file

@ -15,13 +15,13 @@ 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 formData = new FormData();
formData.append("file", file);
if (expiresAt) {
formData.append("expires_at", expiresAt);
}
const response = await fetch(`/storage-api/upload`, {
method: "POST",
body: formData,

View file

@ -1,14 +1,20 @@
import { type NextRequest, NextResponse } from "next/server";
import { authMiddleware } from "~/middlewares/auth_middleware";
import { apisMiddleware } from "./middlewares/api_middleware";
export async function middleware(request: NextRequest) {
//console.log("Middleware triggered for:", request.nextUrl.pathname);
const { pathname } = request.nextUrl;
if (pathname.startsWith("/storage-api/")) {
return await apisMiddleware(request);
}
return (await authMiddleware(request)) ?? NextResponse.next();
}
export const config = {
matcher: [
"/storage-api/:path*",
{
source: "/",
},

View file

@ -0,0 +1,82 @@
import { type NextRequest, NextResponse } from "next/server";
import { env } from "~/env";
import { TOKEN_CONFIG } from "~/server/auth/configs";
import { verifyToken } from "~/server/auth/jwt";
export const apisMiddleware = async (req: NextRequest) => {
const { pathname, searchParams } = req.nextUrl;
console.log("APIs Middleware triggered for:", pathname);
const accessToken = req.cookies.get(
TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME,
)?.value;
if (!accessToken) {
return new NextResponse("Unauthorized", { status: 401 });
}
const payload = await verifyToken(accessToken);
if (!payload) {
return new NextResponse("Unauthorized", { status: 401 });
}
// Build the storage API URL correctly
const slug = pathname.replace("/storage-api/", "");
// Preserve existing query params and add token
const params = new URLSearchParams(searchParams);
params.set("token", env.STORAGE_TOKEN);
const storageUrl = `${env.STORAGE_URL}/${slug}?${params.toString()}`;
try {
// Forward the request to storage API
const headers = new Headers(req.headers);
headers.delete("host");
headers.delete("cookie");
const response = await fetch(storageUrl, {
method: req.method,
headers: headers,
body:
req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined,
// @ts-expect-error - duplex needed for request streaming
duplex: "half",
});
if (!response.ok) {
console.error("Storage API error:", response.status);
return new NextResponse("Storage API error", { status: response.status });
}
// Get response data
const data = await response.arrayBuffer();
// Create response with proper headers
const proxyResponse = new NextResponse(data, {
status: response.status,
statusText: response.statusText,
});
// Copy relevant headers
const contentType = response.headers.get("Content-Type");
const contentDisposition = response.headers.get("Content-Disposition");
const contentLength = response.headers.get("Content-Length");
if (contentType) proxyResponse.headers.set("Content-Type", contentType);
if (contentDisposition)
proxyResponse.headers.set("Content-Disposition", contentDisposition);
if (contentLength)
proxyResponse.headers.set("Content-Length", contentLength);
// Set CORS headers
proxyResponse.headers.set("Access-Control-Allow-Origin", env.BASE_URL);
proxyResponse.headers.set("Access-Control-Allow-Credentials", "true");
proxyResponse.headers.delete("X-Frame-Options"); // Remove if set by storage API
proxyResponse.headers.set(
"Content-Security-Policy",
"frame-ancestors 'self'",
);
return proxyResponse;
} catch (error) {
console.error("Storage proxy error:", error);
return new NextResponse("Failed to proxy request", { status: 500 });
}
};

View file

@ -206,7 +206,7 @@ const VisibComponent = ({
setOpen: (v: boolean) => void;
}) => {
const utils = api.useUtils();
const { data, isLoading } = api.storage.getStorageVisibility.useQuery({
const { data, isLoading } = api.storage.getStorageAccessDetails.useQuery({
storageIds,
});
const { data: users } = api.users.getUsers.useQuery();
@ -219,14 +219,14 @@ const VisibComponent = ({
return { toastId };
},
onSuccess: async (_error, _variables, context) => {
await utils.storage.getStorageVisibility.invalidate();
await utils.storage.getStorageAccessDetails.invalidate();
toast.success("Utente rimosso", {
icon: "🗑️",
id: context?.toastId,
});
},
});
const { mutate: add } = api.storage.addUserStorage.useMutation({
const { mutate: add } = api.storage.addUserStorageEntry.useMutation({
onMutate: () => {
const toastId = toast.loading("Aggiunta utente in corso", {
icon: "🪛",
@ -234,7 +234,7 @@ const VisibComponent = ({
return { toastId };
},
onSuccess: async (_error, _variables, context) => {
await utils.storage.getStorageVisibility.invalidate();
await utils.storage.getStorageAccessDetails.invalidate();
toast.success("Utente aggiunto", {
icon: "👍",
id: context?.toastId,
@ -250,7 +250,7 @@ const VisibComponent = ({
return { toastId };
},
onSuccess: async (_error, _variables, context) => {
await utils.storage.getStorageVisibility.invalidate();
await utils.storage.getStorageAccessDetails.invalidate();
toast.success("Reset effettuato", {
icon: "👍",
id: context?.toastId,

View file

@ -60,10 +60,15 @@ export const storageRouter = createTRPCRouter({
.query(async ({ input }) => {
return await retrieveUserFiles(input);
}),
//OLD
addUserStorage: protectedProcedure
getStorageAccessDetails: adminProcedure
.input(z.object({ storageIds: z.string().array() }))
.query(async ({ input }) => {
return await getMultipleWithRelations({
db,
ids: input.storageIds,
});
}),
addUserStorageEntry: protectedProcedure
.input(z.custom<NewUsersStorage>())
.mutation(async ({ input }) => {
return await addUserStorage(input);
@ -73,15 +78,6 @@ export const storageRouter = createTRPCRouter({
return await getAllWithFromAdmin();
}),
getStorageVisibility: adminProcedure
.input(z.object({ storageIds: z.string().array() }))
.query(async ({ input }) => {
return await getMultipleWithRelations({
db,
ids: input.storageIds,
});
}),
renameFile: adminProcedure
.input(z.object({ fileId: z.string(), name: z.string() }))
.mutation(async ({ input }) => {