diff --git a/docs/notes.md b/docs/notes.md index 9fb8c6a..52ff0aa 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -355,4 +355,18 @@ Now we will go now and add metadata Now we have the metadata along with some great filters on the data board page -Now we are going to implement an an update of a file or folder stored. 12/1/2026 \ No newline at end of file +Now we are going to implement an an update of a file or folder stored. 12/1/2026 + +For download there 2 function we need to implement + +1) down load the file to ~/user/Downloads, this should work for any file type including pdf + +2) if the file is a pdf open the file in another tab in the browser as most browser support reading a pdf + +For download there 2 function we need to implement + +1) down load the file to ~/user/Downloads, this should work for any file type including pdf + +2) if the file is a pdf open the file in another tab in the browser as most browser support reading a pdf + +So we need to implement 2 new actions, downLoad file and open pdf to read. Does this make sense ? \ No newline at end of file diff --git a/docs/notes.pdf b/docs/notes.pdf index b1ea826..ddf164a 100644 Binary files a/docs/notes.pdf and b/docs/notes.pdf differ diff --git a/src/app/api/download/route.ts b/src/app/api/download/route.ts new file mode 100644 index 0000000..dbdec41 --- /dev/null +++ b/src/app/api/download/route.ts @@ -0,0 +1,66 @@ +// src/app/api/download/route.ts +"use server" + +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from "@/auth"; // Import the auth function from your central config +import { prisma } from '@/lib/prisma'; + +export async function GET(request: NextRequest) { + try { + // 1. Check Authentication using the V5 auth() helper + const session = await auth(); + + // In V5, tokens are usually handled in the session callback + if (!session || !session.accessToken) { + return new NextResponse("Unauthorized - No Access Token found", { status: 401 }); + } + + // 2. Get parameters from URL + const { searchParams } = new URL(request.url); + const fileNodeId = searchParams.get('id'); + const mode = searchParams.get('mode') === 'inline' ? 'inline' : 'attachment'; + + if (!fileNodeId) { + return new NextResponse("File ID is required", { status: 400 }); + } + + // 3. Find the file in your Postgres FileNode table + const fileNode = await prisma.fileNode.findUnique({ + where: { id: fileNodeId } + }); + + if (!fileNode || !fileNode.oneDriveId) { + return new NextResponse("File not found in database", { status: 404 }); + } + + // 4. Fetch the file stream from Microsoft Graph + const graphResponse = await fetch( + `https://graph.microsoft.com/v1.0/me/drive/items/${fileNode.oneDriveId}/content`, + { + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, + } + ); + + if (!graphResponse.ok) { + const errorText = await graphResponse.text(); + console.error('MS Graph Error:', errorText); + return new NextResponse(`OneDrive Error: ${graphResponse.statusText}`, { status: graphResponse.status }); + } + + // 5. Stream the response directly to the client + return new NextResponse(graphResponse.body, { + status: 200, + headers: { + 'Content-Type': fileNode.mimeType || 'application/octet-stream', + // Note: Using encodeURIComponent for filename to handle special characters + 'Content-Disposition': `${mode}; filename="${encodeURIComponent(fileNode.name)}"`, + }, + }); + + } catch (error) { + console.error('Download error:', error); + return new NextResponse("Internal Server Error", { status: 500 }); + } +} \ No newline at end of file diff --git a/src/auth.ts b/src/auth.ts index 4538d2c..8d39e9d 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,3 +1,4 @@ +// src/auth.ts import NextAuth from "next-auth"; import { PrismaAdapter } from "@auth/prisma-adapter"; import { prisma } from "@/lib/prisma"; @@ -10,6 +11,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ callbacks: { async jwt({ token, account, user }) { // 1. Handle OAuth tokens (from first sign-in) + // This captures the tokens directly from the Microsoft Azure response if (account) { token.accessToken = account.access_token; token.refreshToken = account.refresh_token; @@ -17,10 +19,10 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ } // 2. Attach User ID and Role to the token - // When 'user' exists, it's the first time we've fetched this user from the DB during login + // This runs when the user first logs in if (user) { token.sub = user.id; - // @ts-ignore - 'role' exists on our custom User model + // @ts-ignore - 'role' is a custom field in your Postgres User table token.role = user.role; } @@ -29,10 +31,16 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ async session({ session, token }) { // 3. Pass values from the JWT Token into the Client-facing Session - if (session?.user && token.sub) { - session.user.id = token.sub; - // @ts-ignore - Attaching the role so the Navbar and Settings page can see it - session.user.role = token.role; + // This makes the tokens and IDs available to your API routes and Components + if (session?.user) { + session.user.id = token.sub as string; + + // @ts-ignore - Attaching the role for UI permissions + session.user.role = token.role as string; + + // IMPORTANT: We must attach the accessToken here so the + // /api/download route can use it to fetch from MS Graph + session.accessToken = token.accessToken as string; } return session; }, diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts new file mode 100644 index 0000000..1ea228a --- /dev/null +++ b/src/types/next-auth.d.ts @@ -0,0 +1,24 @@ +// src/types/next-auth.d.ts +import NextAuth, { DefaultSession } from "next-auth" +import { JWT } from "next-auth/jwt" + +declare module "next-auth" { + /** + * Returned by `useSession`, `auth`, and received as a prop on the `SessionProvider` React Context + */ + interface Session { + accessToken?: string; + user: { + id: string; + role?: string; + } & DefaultSession["user"] + } +} + +declare module "next-auth/jwt" { + /** Returned by the `jwt` callback and `getToken`, when using JWT sessions */ + interface JWT { + accessToken?: string; + role?: string; + } +} \ No newline at end of file