import 'dotenv/config.js'; import https from 'https'; import fs from 'fs'; import express from 'express'; import cors from 'cors'; import { ConfidentialClientApplication } from '@azure/msal-node'; import fetch from 'node-fetch'; const app = express(); app.use(cors()); app.use(express.json({ limit: '50mb' })); const PORT = process.env.PORT || 3001; // MSAL configuration for client credentials const msalConfig = { auth: { clientId: process.env.CLIENT_ID, authority: `https://login.microsoftonline.com/${process.env.TENANT_ID}`, clientSecret: process.env.CLIENT_SECRET, }, }; const cca = new ConfidentialClientApplication(msalConfig); // Endpoint to exchange On-Behalf-Of token (legacy) app.post('/api/obo-exchange', async (req, res) => { const { userToken } = req.body; if (!userToken) { return res.status(400).json({ error: 'User token required' }); } try { const response = await cca.acquireTokenOnBehalfOf({ scopes: ['https://graph.microsoft.com/.default'], oboAssertion: userToken, }); res.json({ token: response.accessToken }); } catch (error) { console.error('OBO exchange error:', error); res.status(500).json({ error: error.message }); } }); // Main endpoint: create folder + upload PDF // Uses On-Behalf-Of flow: frontend sends user token → backend exchanges for Graph token app.post('/api/upload-pdf', async (req, res) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Missing or invalid Authorization header' }); } const userAssertionToken = authHeader.substring(7); // Remove "Bearer " prefix const { jobNumber, jobFullName, fileName, pdfBase64 } = req.body; if (!jobNumber || !jobFullName || !fileName || !pdfBase64) { return res.status(400).json({ error: 'Missing required fields' }); } try { // Exchange user token for Graph token using On-Behalf-Of flow console.log('Exchanging user token via OBO flow...'); const tokenResponse = await cca.acquireTokenOnBehalfOf({ oboAssertion: userAssertionToken, scopes: ['https://graph.microsoft.com/.default'], }); const accessToken = tokenResponse.accessToken; console.log('Token exchanged successfully. Proceeding with Graph API calls...'); const driveId = process.env.DRIVE_ID; const parentId = process.env.PARENT_ITEM_ID; // Create main folder: "Job_Number - Job_Full_Name" const mainFolderName = `${jobNumber} - ${jobFullName}`; const createFolderUrl = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${parentId}/children`; const createFolderBody = { name: mainFolderName, folder: {}, '@microsoft.graph.conflictBehavior': 'rename', }; const createFolderResp = await fetch(createFolderUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify(createFolderBody), }); if (!createFolderResp.ok) { const errorData = await createFolderResp.text(); console.error('Create folder error:', errorData); return res.status(500).json({ error: `Create folder failed: ${errorData}` }); } const mainFolder = await createFolderResp.json(); const mainFolderId = mainFolder.id; console.log(`✓ Created main folder: ${mainFolderName} (ID: ${mainFolderId})`); // Create "Managers Info" subfolder const subfolderName = 'Managers Info'; const createSubfolderUrl = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${mainFolderId}/children`; const createSubfolderBody = { name: subfolderName, folder: {}, '@microsoft.graph.conflictBehavior': 'rename', }; const createSubfolderResp = await fetch(createSubfolderUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify(createSubfolderBody), }); if (!createSubfolderResp.ok) { const errorData = await createSubfolderResp.text(); console.error('Create subfolder error:', errorData); return res.status(500).json({ error: `Create subfolder failed: ${errorData}` }); } const subfolder = await createSubfolderResp.json(); const subfolderId = subfolder.id; console.log(`✓ Created subfolder: ${subfolderName} (ID: ${subfolderId})`); // Upload PDF to subfolder const pdfBuffer = Buffer.from(pdfBase64, 'base64'); const uploadUrl = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${subfolderId}:/${encodeURIComponent(fileName)}:/content`; const uploadResp = await fetch(uploadUrl, { method: 'PUT', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/pdf', }, body: pdfBuffer, }); if (!uploadResp.ok) { const errorData = await uploadResp.text(); console.error('Upload PDF error:', errorData); return res.status(500).json({ error: `Upload PDF failed: ${errorData}` }); } const uploadedItem = await uploadResp.json(); console.log(`✓ Uploaded PDF: ${fileName}`); // Create share link for the subfolder const shareLinkUrl = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${subfolderId}/createLink`; const shareLinkBody = { type: 'view', scope: 'organization', }; const shareLinkResp = await fetch(shareLinkUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify(shareLinkBody), }); if (!shareLinkResp.ok) { const errorData = await shareLinkResp.text(); console.error('Create share link error:', errorData); return res.status(500).json({ error: `Create share link failed: ${errorData}` }); } const shareLink = await shareLinkResp.json(); console.log(`✓ Created share link`); res.json({ uploadedItem, shareLink: shareLink.link.webUrl, }); } catch (error) { console.error('Upload PDF error:', error); res.status(500).json({ error: error.message }); } }); // Health check app.get('/', (req, res) => { res.json({ message: 'Backend server running' }); }); // Fallback to HTTP (dev server doesn't need HTTPS for localhost) app.listen(PORT, () => { console.log(`✅ Backend listening on http://localhost:${PORT}`); });