import { Router } from 'express'; import { fetchGraph, postGraph } from '../utils/index.js'; import { AuthManager } from '../auth/index.js'; export default (auth: AuthManager) => { const router = Router(); // Helper to get token const getToken = async (req: any, res: any) => { const sessionId = req.header('x-session-id'); if (!sessionId) { res.status(400).json({ error: 'Missing x-session-id header' }); return null; } try { return await auth.acquireTokenBySessionId(sessionId); } catch (err: any) { const status = err.message.includes('expired') || err.message.includes('not found') ? 401 : 500; res.status(status).json({ error: 'authentication_required', message: err.message, hint: err.message.includes('expired') ? 'Device code has expired. Please call POST /login again.' : 'Please call POST /login first to authenticate', }); return null; } }; // GET /me/drive router.get('/', async (req, res) => { const token = await getToken(req, res); if (!token) return; try { const data = await fetchGraph('/me/drive', token); res.json(data); } catch (error: any) { console.error('Error fetching drive:', error.message); res.status(500).json({ error: error.message }); } }); // GET /me/drive/root/children router.get('/root/children', async (req, res) => { const token = await getToken(req, res); if (!token) return; try { const data = await fetchGraph('/me/drive/root/children', token); res.json(data); } catch (error: any) { console.error('Error fetching root children:', error.message); res.status(500).json({ error: error.message }); } }); // GET /me/drive/recent router.get('/recent', async (req, res) => { const token = await getToken(req, res); if (!token) return; try { const data = await fetchGraph('/me/drive/recent', token); res.json(data); } catch (error: any) { console.error('Error fetching recent files:', error.message); res.status(500).json({ error: error.message }); } }); // GET /me/drive/sharedWithMe router.get('/sharedWithMe', async (req, res) => { const token = await getToken(req, res); if (!token) return; try { const data = await fetchGraph('/me/drive/sharedWithMe', token); res.json(data); } catch (error: any) { console.error('Error fetching shared files:', error.message); res.status(500).json({ error: error.message }); } }); // GET /me/drive/search router.get('/search', async (req, res) => { const token = await getToken(req, res); if (!token) return; const q = req.query.q; if (!q) { return res.status(400).json({ error: 'Missing query parameter q' }); } try { // Use single quotes for the search term const data = await fetchGraph(`/me/drive/root/search(q='${q}')`, token); res.json(data); } catch (error: any) { console.error('Error searching drive:', error.message); res.status(500).json({ error: error.message }); } }); // GET /me/drive/items/:id router.get('/items/:id', async (req, res) => { const token = await getToken(req, res); if (!token) return; const { id } = req.params; try { const data = await fetchGraph(`/me/drive/items/${id}`, token); res.json(data); } catch (error: any) { console.error(`Error fetching item ${id}:`, error.message); res.status(500).json({ error: error.message }); } }); // GET /me/drive/items/:id/children router.get('/items/:id/children', async (req, res) => { const token = await getToken(req, res); if (!token) return; const { id } = req.params; try { const data = await fetchGraph(`/me/drive/items/${id}/children`, token); res.json(data); } catch (error: any) { console.error(`Error fetching item ${id} children:`, error.message); res.status(500).json({ error: error.message }); } }); // POST /me/drive/items/:id/children (Create Folder) router.post('/items/:id/children', async (req, res) => { const token = await getToken(req, res); if (!token) return; const { id } = req.params; const { name } = req.body; if (!name) { return res.status(400).json({ error: 'Missing folder name' }); } const body = { name, folder: {}, '@microsoft.graph.conflictBehavior': 'rename' }; try { const data = await postGraph(`/me/drive/items/${id}/children`, token, body); res.json(data); } catch (error: any) { console.error(`Error creating folder in ${id}:`, error.message); res.status(500).json({ error: error.message }); } }); // POST /me/drive/root/children (Create Folder in Root) router.post('/root/children', async (req, res) => { const token = await getToken(req, res); if (!token) return; const { name } = req.body; if (!name) { return res.status(400).json({ error: 'Missing folder name' }); } const body = { name, folder: {}, '@microsoft.graph.conflictBehavior': 'rename' }; try { const data = await postGraph('/me/drive/root/children', token, body); res.json(data); } catch (error: any) { console.error('Error creating folder in root:', error.message); res.status(500).json({ error: error.message }); } }); // GET /me/drive/items/:id/content (Download - Redirect) router.get('/items/:id/content', async (req, res) => { const token = await getToken(req, res); if (!token) return; const { id } = req.params; try { // We first get the item to check for downloadUrl const item = await fetchGraph(`/me/drive/items/${id}`, token); if (item['@microsoft.graph.downloadUrl']) { // Redirect to the download URL return res.redirect(item['@microsoft.graph.downloadUrl']); } // If no downloadUrl, try accessing /content endpoint which should be a stream // But fetchGraph returns JSON. We might need to handle this differently if we wanted to proxy the stream. // Redirecting user to the graph endpoint with token might be unsafe if they are in browser (CORS?), // but usually the @microsoft.graph.downloadUrl is a pre-authenticated link. res.status(404).json({ error: 'Download URL not found for this item' }); } catch (error: any) { console.error(`Error getting content for ${id}:`, error.message); res.status(500).json({ error: error.message }); } }); // POST /me/drive/items/:id/createLink (Share) router.post('/items/:id/createLink', async (req, res) => { const token = await getToken(req, res); if (!token) return; const { id } = req.params; const { type = 'view', scope = 'anonymous' } = req.body; // default to view and anonymous const body = { type, scope }; try { const data = await postGraph(`/me/drive/items/${id}/createLink`, token, body); res.json(data); } catch (error: any) { console.error(`Error creating link for ${id}:`, error.message); res.status(500).json({ error: error.message }); } }); // POST /me/drive/items/:id/invite (Share / Invite) router.post('/items/:id/invite', async (req, res) => { const token = await getToken(req, res); if (!token) return; const { id } = req.params; const { recipients, message, requireSignIn, sendInvitation, allBody } = req.body; // Allow passing full body or just simplified fields const body = allBody || req.body; try { const data = await postGraph(`/me/drive/items/${id}/invite`, token, body); res.json(data); } catch (error: any) { console.error(`Error inviting to ${id}:`, error.message); res.status(500).json({ error: error.message }); } }); return router; };