Files

195 lines
6.4 KiB
JavaScript

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}`);
});