diff --git a/apps/infoalloggi/Dockerfile b/apps/infoalloggi/Dockerfile index 394850e..b88b082 100644 --- a/apps/infoalloggi/Dockerfile +++ b/apps/infoalloggi/Dockerfile @@ -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 diff --git a/apps/infoalloggi/next.config.ts b/apps/infoalloggi/next.config.ts index 315b2d7..70d4ada 100644 --- a/apps/infoalloggi/next.config.ts +++ b/apps/infoalloggi/next.config.ts @@ -59,10 +59,6 @@ async function createNextConfig(): Promise { 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 { 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: - "(?^[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: - "(?^[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*", - }, ]; }, diff --git a/apps/infoalloggi/src/components/servizio/annuncio_actions.tsx b/apps/infoalloggi/src/components/servizio/annuncio_actions.tsx index 7275601..2308515 100644 --- a/apps/infoalloggi/src/components/servizio/annuncio_actions.tsx +++ b/apps/infoalloggi/src/components/servizio/annuncio_actions.tsx @@ -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 ; if (files === undefined) { diff --git a/apps/infoalloggi/src/components/tables/storage-table.tsx b/apps/infoalloggi/src/components/tables/storage-table.tsx index 10d106d..214fe6d 100644 --- a/apps/infoalloggi/src/components/tables/storage-table.tsx +++ b/apps/infoalloggi/src/components/tables/storage-table.tsx @@ -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) => { 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 ( - {t.modifica} - modifica + Rinomina + Rinomina
@@ -323,10 +326,10 @@ const RinominaDialog = ({ defaultValue={renameTarget.filename || undefined} minLength={5} name="filename" - placeholder="Modifica messaggio" + placeholder="Rinomina File" type="text" /> - +
diff --git a/apps/infoalloggi/src/components/upload_modal.tsx b/apps/infoalloggi/src/components/upload_modal.tsx index 4e76503..6ff67e3 100644 --- a/apps/infoalloggi/src/components/upload_modal.tsx +++ b/apps/infoalloggi/src/components/upload_modal.tsx @@ -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: "📤" }); }, diff --git a/apps/infoalloggi/src/hooks/storageClienSideHooks.ts b/apps/infoalloggi/src/hooks/storageClienSideHooks.ts index fb23191..03825e5 100644 --- a/apps/infoalloggi/src/hooks/storageClienSideHooks.ts +++ b/apps/infoalloggi/src/hooks/storageClienSideHooks.ts @@ -15,13 +15,13 @@ export async function uploadFile( file: File, expiresAt: string | undefined, ): Promise { - 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, diff --git a/apps/infoalloggi/src/middleware.ts b/apps/infoalloggi/src/middleware.ts index 2664eee..fb52200 100644 --- a/apps/infoalloggi/src/middleware.ts +++ b/apps/infoalloggi/src/middleware.ts @@ -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: "/", }, diff --git a/apps/infoalloggi/src/middlewares/api_middleware.ts b/apps/infoalloggi/src/middlewares/api_middleware.ts new file mode 100644 index 0000000..28706fd --- /dev/null +++ b/apps/infoalloggi/src/middlewares/api_middleware.ts @@ -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 }); + } +}; diff --git a/apps/infoalloggi/src/pages/area-riservata/admin/storage.tsx b/apps/infoalloggi/src/pages/area-riservata/admin/storage.tsx index a437c78..469f06e 100644 --- a/apps/infoalloggi/src/pages/area-riservata/admin/storage.tsx +++ b/apps/infoalloggi/src/pages/area-riservata/admin/storage.tsx @@ -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, diff --git a/apps/infoalloggi/src/server/api/routers/storage.ts b/apps/infoalloggi/src/server/api/routers/storage.ts index 5298a2b..39292eb 100644 --- a/apps/infoalloggi/src/server/api/routers/storage.ts +++ b/apps/infoalloggi/src/server/api/routers/storage.ts @@ -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()) .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 }) => {