diff --git a/backend/server.ts b/backend/server.ts
index d9630d1..90a7978 100644
--- a/backend/server.ts
+++ b/backend/server.ts
@@ -185,9 +185,56 @@ const fetchJson = async (url: string, token: string) => {
};
const resolveShareLink = async (link: string, token: string) => {
- const encoded = `u!${b64Url(link)}`;
- const data = await fetchJson(`https://graph.microsoft.com/v1.0/shares/${encoded}/driveItem`, token);
- return { driveId: data.parentReference?.driveId as string, itemId: data.id as string };
+ // Decode the link in case it comes pre-encoded from the database
+ const decodedLink = decodeURIComponent(link);
+ console.log('[resolveShareLink] Decoded link:', decodedLink);
+
+ // Check if this is a short sharing link (/:f:/ or /:b:/) or a direct document library URL
+ if (decodedLink.includes('/:f:/') || decodedLink.includes('/:b:/')) {
+ // Short sharing link - use shares API
+ const encoded = `u!${b64Url(decodedLink)}`;
+ console.log('[resolveShareLink] Using shares API, encoded:', encoded);
+ const data = await fetchJson(`https://graph.microsoft.com/v1.0/shares/${encoded}/driveItem`, token);
+ return { driveId: data.parentReference?.driveId as string, itemId: data.id as string };
+ } else {
+ // Direct document library URL - parse it and use drive API
+ // Example: https://czflex.sharepoint.com/sites/Team/Shared Documents/Forms/AllItems.aspx?RootFolder=/sites/Team/Shared Documents/General/...
+ const url = new URL(decodedLink);
+ const pathMatch = url.hostname.match(/^([^.]+)\.sharepoint\.com$/);
+ if (!pathMatch) throw new Error('Invalid SharePoint URL');
+
+ const hostname = pathMatch[1]; // e.g., 'czflex'
+ const sitePath = url.pathname.split('/Shared')[0]; // e.g., /sites/Team
+ const rootFolderParam = url.searchParams.get('RootFolder');
+ if (!rootFolderParam) throw new Error('RootFolder parameter missing');
+
+ // Extract the folder path relative to the document library
+ // RootFolder format: /sites/Team/Shared Documents/General/Operations [Server]/...
+ // We need: General/Operations [Server]/...
+ const folderPath = rootFolderParam.split('/Shared Documents/')[1];
+ if (!folderPath) throw new Error('Could not parse folder path');
+
+ console.log('[resolveShareLink] Hostname:', hostname);
+ console.log('[resolveShareLink] Site path:', sitePath);
+ console.log('[resolveShareLink] Folder path:', folderPath);
+
+ // Get the site ID first using hostname:sitePath format
+ const siteData = await fetchJson(`https://graph.microsoft.com/v1.0/sites/${hostname}.sharepoint.com:${sitePath}`, token);
+ const siteId = siteData.id;
+
+ // Get the default document library drive
+ const driveData = await fetchJson(`https://graph.microsoft.com/v1.0/sites/${siteId}/drive`, token);
+ const driveId = driveData.id;
+
+ // Get the folder item by path
+ const itemData = await fetchJson(
+ `https://graph.microsoft.com/v1.0/drives/${driveId}/root:/${folderPath}`,
+ token
+ );
+
+ console.log('[resolveShareLink] Resolved to driveId:', driveId, 'itemId:', itemData.id);
+ return { driveId, itemId: itemData.id };
+ }
};
const listChildrenRecursive = async (
@@ -225,9 +272,12 @@ const filterByCategory = (items: DriveItem[], category?: string, pdfOnly: boolea
if (i.folder) return false;
const name = i.name.toLowerCase();
const mimeType = i.file?.mimeType?.toLowerCase() || '';
- // Include PDFs and Word documents
+ // Include PDFs, Word documents, and images
return name.endsWith('.pdf') || mimeType.includes('pdf') ||
- name.endsWith('.doc') || name.endsWith('.docx') || mimeType.includes('word');
+ name.endsWith('.doc') || name.endsWith('.docx') || mimeType.includes('word') ||
+ name.endsWith('.jpg') || name.endsWith('.jpeg') || name.endsWith('.png') ||
+ name.endsWith('.gif') || name.endsWith('.bmp') || name.endsWith('.tiff') ||
+ name.endsWith('.webp') || mimeType.includes('image');
});
}
@@ -274,6 +324,7 @@ app.get('/api/job-files', async (c) => {
// Filter to show only PDFs by default (can be disabled with pdfOnly=false query param)
const pdfOnly = c.req.query('pdfOnly') !== 'false';
const filtered = filterByCategory(items, category, pdfOnly);
+ console.log(`[job-files] Found ${items.length} total items, ${filtered.length} after filtering`);
const mapped = filtered.map((i) => ({
id: i.id,
@@ -291,6 +342,7 @@ app.get('/api/job-files', async (c) => {
} catch (err) {
const message = (err as Error)?.message || String(err);
const status = (err as any)?.status || 500;
+ console.error('[job-files] ERROR:', message);
logLine('logs/error.log', `/api/job-files error: ${message}`);
return c.json({ error: message }, status);
}
diff --git a/frontend/index.html b/frontend/index.html
index b23c83a..22de787 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -67,7 +67,7 @@