Skip to content

Commit

Permalink
Added a proxy using NextJS middleware to download user files
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianLeChat committed Jan 4, 2024
1 parent a554e53 commit b7c4066
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 14 deletions.
12 changes: 4 additions & 8 deletions app/[locale]/dashboard/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

"use server";

import mime from "mime";
import prisma from "@/utilities/prisma";
import schema from "@/schemas/file-upload";
import { auth } from "@/utilities/next-auth";
Expand Down Expand Up @@ -103,20 +102,17 @@ export async function uploadFiles(
const identifier = (
await prisma.file.create( {
data: {
name: parse( file.name ).name,
name: file.name,
userId: user.id,
status: "public"
status: "private"
}
} )
).fileId;

// On écrit alors le fichier dans le système de fichiers
// avec l'identifiant unique généré précédemment.
await writeFile(
join(
userFolder,
`${ identifier }.${ mime.getExtension( file.type ) }`
),
join( userFolder, `${ identifier + parse( file.name ).ext }` ),
new Uint8Array( await file.arrayBuffer() )
);
} );
Expand All @@ -136,8 +132,8 @@ export async function uploadFiles(
// à travers le réseau vers les composants clients.
// Source : https://github.com/vercel/next.js/issues/47447
id: currentFiles.length + index,
name: parse( file.name ).name,
size: file.size,
name: file.name,
type: file.type
} ) )
};
Expand Down
3 changes: 3 additions & 0 deletions app/[locale]/dashboard/components/row-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// Composant des actions disponibles pour une ligne d'un tableau de données.
// Source : https://ui.shadcn.com/docs/components/data-table
//

"use client";

import { merge } from "@/utilities/tailwind";
import { Ban,
Check,
Expand Down
6 changes: 3 additions & 3 deletions app/[locale]/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@ async function getFiles(): Promise<File[]>
// On retourne enfin les propriétés de chaque fichier
// associé avec leur nom d'origine.
const stats = await stat( join( userFolder, file ) );
const result = await prisma.file.findFirst( {
const result = await prisma.file.findUnique( {
where: {
fileId: parse( file ).name
}
} );

return {
id: index,
name: result?.name ?? parse( file ).name,
name: parse( result?.name ?? file ).name,
type: mime.getType( file ) ?? "application/octet-stream",
size: stats.size,
date: stats.birthtime.toISOString(),
status: "public"
status: result?.status ?? "public"
} as File;
} )
);
Expand Down
44 changes: 44 additions & 0 deletions app/api/storage/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Route pour la récupération des fichiers utilisateurs.
// Source : https://github.com/prisma/prisma/discussions/12602
//
import prisma from "@/utilities/prisma";
import { parse } from "path";
import { NextResponse } from "next/server";

export async function GET(
request: Request,
{ params }: { params: { id: string } }
)
{
// On récupère d'abord les informations du fichier
// à partir de son identifiant dans la base de données.
const file = await prisma.file.findUnique( {
where: {
fileId: params.id
}
} );

if ( !file )
{
// Si le fichier n'existe pas, on retourne une erreur 404.
return new NextResponse( null, { status: 404 } );
}

// Dans le cas contraire, on vérifie ensuite si le fichier est
// public ou non.
if ( file.status === "public" )
{
// Si le fichier est public, on retourne une redirection
// vers le fichier.
return new NextResponse(
new URL(
`/storage/${ file.userId }/${ file.fileId + parse( file.name ).ext }`,
request.url
).href
);
}

// Sinon, on retourne enfin une erreur d'authentification.
return new NextResponse( null, { status: 403 } );
}
40 changes: 37 additions & 3 deletions middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,45 @@ import type { RecaptchaResponse } from "./interfaces/Recaptcha";

export default async function middleware( request: NextRequest )
{
// On vérifie d'abord si le service reCAPTCHA est activé ou non
// On vérifie d'abord si la requête courante est de type GET
// et si elle cherche à accéder à un fichier utilisateur.
if (
request.method === "GET"
&& request.nextUrl.pathname.startsWith( "/d/" )
)
{
// Si c'est le cas, on récupère l'identifiant du fichier
// à partir de l'URL de la requête.
const identifier = request.nextUrl.pathname.split( "/d/" )[ 1 ];

if ( identifier )
{
// On fait une requête à l'API pour récupérer le chemin
// du fichier à partir de son identifiant.
const data = await fetch(
new URL(
`${ process.env.__NEXT_ROUTER_BASEPATH }/api/storage/${ identifier }`,
request.url
)
);

if ( data.ok )
{
// Si l'API semble avoir traitée la requête avec succès,
// on retourne une redirection vers le fichier.
return NextResponse.rewrite( await data.text() );
}
}
}

// On vérifie ensuite si le service reCAPTCHA est activé ou non
// et s'il s'agit d'une requête de type POST.
if (
process.env.NEXT_PUBLIC_RECAPTCHA_ENABLED === "true"
&& request.method === "POST"
)
{
// On récupère ensuite la requête sous format de formulaire avant
// On récupère alors la requête sous format de formulaire avant
// de vérifier si elle contient un jeton d'authentification.
const token = ( await request.formData() ).get( "1_recaptcha" );

Expand Down Expand Up @@ -68,7 +99,10 @@ export default async function middleware( request: NextRequest )
}

export const config = {
matcher: [ "/", "/((?!api/admin|api/auth|_next|_vercel|.*\\..*).*)" ]
matcher: [
"/",
"/((?!api/admin|api/auth|api/storage|_next|_vercel|.*\\..*).*)"
]
};

if ( process.env.__NEXT_ROUTER_BASEPATH )
Expand Down

0 comments on commit b7c4066

Please sign in to comment.