195 lines
6.4 KiB
JavaScript
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}`);
|
|
});
|