| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670 | 
							- /**
 
-  * Universal File Server
 
-  * Ensures all attachments and avatars are always visible regardless of ROOT_URL and PORT settings
 
-  * Handles both new Meteor-Files and legacy CollectionFS file serving
 
-  */
 
- import { Meteor } from 'meteor/meteor';
 
- import { WebApp } from 'meteor/webapp';
 
- import { ReactiveCache } from '/imports/reactiveCache';
 
- import { Accounts } from 'meteor/accounts-base';
 
- import Attachments, { fileStoreStrategyFactory as attachmentStoreFactory } from '/models/attachments';
 
- import Avatars, { fileStoreStrategyFactory as avatarStoreFactory } from '/models/avatars';
 
- import '/models/boards';
 
- import { getAttachmentWithBackwardCompatibility, getOldAttachmentStream } from '/models/lib/attachmentBackwardCompatibility';
 
- import fs from 'fs';
 
- import path from 'path';
 
- if (Meteor.isServer) {
 
-   console.log('Universal file server initializing...');
 
-   /**
 
-    * Helper function to set appropriate headers for file serving
 
-    */
 
-   function setFileHeaders(res, fileObj, isAttachment = false) {
 
-     // Decide safe serving strategy
 
-     const nameLower = (fileObj.name || '').toLowerCase();
 
-     const typeLower = (fileObj.type || '').toLowerCase();
 
-     const isPdfByExt = nameLower.endsWith('.pdf');
 
-     
 
-     // Define dangerous types that must never be served inline
 
-     const dangerousTypes = new Set([
 
-       'text/html',
 
-       'application/xhtml+xml',
 
-       'image/svg+xml',
 
-       'text/xml',
 
-       'application/xml',
 
-       'application/javascript',
 
-       'text/javascript'
 
-     ]);
 
-     
 
-     // Define safe types that can be served inline for viewing
 
-     const safeInlineTypes = new Set([
 
-       'application/pdf',
 
-       'image/jpeg',
 
-       'image/jpg',
 
-       'image/png',
 
-       'image/gif',
 
-       'image/webp',
 
-       'image/avif',
 
-       'image/bmp',
 
-       'video/mp4',
 
-       'video/webm',
 
-       'video/ogg',
 
-       'audio/mpeg',
 
-       'audio/mp3',
 
-       'audio/ogg',
 
-       'audio/wav',
 
-       'audio/webm',
 
-       'text/plain',
 
-       'application/json'
 
-     ]);
 
-     
 
-     const isSvg = nameLower.endsWith('.svg') || typeLower === 'image/svg+xml';
 
-   const isDangerous = dangerousTypes.has(typeLower) || isSvg;
 
-   // Consider PDF safe inline by extension if type is missing/mis-set
 
-   const isSafeInline = safeInlineTypes.has(typeLower) || (isAttachment && isPdfByExt);
 
-     // Always send strong caching and integrity headers
 
-     res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for 1 year
 
-     res.setHeader('ETag', `"${fileObj._id}"`);
 
-     res.setHeader('X-Content-Type-Options', 'nosniff');
 
-     // Set content length when available
 
-     if (fileObj.size) {
 
-       res.setHeader('Content-Length', fileObj.size);
 
-     }
 
-     if (isAttachment) {
 
-       // Attachments: dangerous types forced to download, safe types can be inline
 
-       if (isDangerous) {
 
-         // SECURITY: Force download for dangerous types to prevent XSS
 
-         res.setHeader('Content-Type', 'application/octet-stream');
 
-         res.setHeader('Content-Disposition', `attachment; filename="${fileObj.name}"`);
 
-         res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;");
 
-         res.setHeader('X-Frame-Options', 'DENY');
 
-       } else if (isSafeInline) {
 
-         // Safe types: serve inline with proper type and restrictive CSP
 
-         // If the file is a PDF by extension but type is wrong/missing, correct it
 
-         const finalType = (isPdfByExt && typeLower !== 'application/pdf') ? 'application/pdf' : (typeLower || 'application/octet-stream');
 
-         res.setHeader('Content-Type', finalType);
 
-         res.setHeader('Content-Disposition', `inline; filename="${fileObj.name}"`);
 
-         // Restrictive CSP for safe types - allow media/img/object for viewer embeds, no scripts
 
-         res.setHeader('Content-Security-Policy', "default-src 'none'; object-src 'self'; media-src 'self'; img-src 'self'; style-src 'unsafe-inline';");
 
-       } else {
 
-         // Unknown types: force download as fallback
 
-         res.setHeader('Content-Type', 'application/octet-stream');
 
-         res.setHeader('Content-Disposition', `attachment; filename="${fileObj.name}"`);
 
-         res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;");
 
-       }
 
-     } else {
 
-       // Avatars: allow inline images, but never serve SVG inline
 
-       if (isSvg || isDangerous) {
 
-         // Serve potentially dangerous avatar types as downloads instead
 
-         res.setHeader('Content-Type', 'application/octet-stream');
 
-         res.setHeader('Content-Disposition', `attachment; filename="${fileObj.name}"`);
 
-         res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;");
 
-         res.setHeader('X-Frame-Options', 'DENY');
 
-       } else {
 
-         // For typical image avatars, use provided type if present, otherwise fall back to a safe generic image type
 
-         res.setHeader('Content-Type', typeLower || 'image/jpeg');
 
-         res.setHeader('Content-Disposition', `inline; filename="${fileObj.name}"`);
 
-       }
 
-     }
 
-   }
 
-   /**
 
-    * Helper function to handle conditional requests
 
-    */
 
-   function handleConditionalRequest(req, res, fileObj) {
 
-     const ifNoneMatch = req.headers['if-none-match'];
 
-     if (ifNoneMatch && ifNoneMatch === `"${fileObj._id}"`) {
 
-       res.writeHead(304);
 
-       res.end();
 
-       return true;
 
-     }
 
-     return false;
 
-   }
 
-   /**
 
-    * Extract first path segment (file id) from request URL.
 
-    * Works whether req.url is the full path or already trimmed by the mount path.
 
-    */
 
-   function extractFirstIdFromUrl(req, mountPrefix) {
 
-     // Strip query string
 
-     let urlPath = (req.url || '').split('?')[0];
 
-     // If url still contains the mount prefix, remove it
 
-     if (mountPrefix && urlPath.startsWith(mountPrefix)) {
 
-       urlPath = urlPath.slice(mountPrefix.length);
 
-     }
 
-     // Ensure leading slash removed for splitting
 
-     if (urlPath.startsWith('/')) {
 
-       urlPath = urlPath.slice(1);
 
-     }
 
-     const parts = urlPath.split('/').filter(Boolean);
 
-     return parts[0] || null;
 
-   }
 
-   /**
 
-    * Check if the request explicitly asks to download the file
 
-    * Recognizes ?download=true or ?download=1 (case-insensitive for key)
 
-    */
 
-   function isDownloadRequested(req) {
 
-     const q = (req.url || '').split('?')[1] || '';
 
-     if (!q) return false;
 
-     const pairs = q.split('&');
 
-     for (const p of pairs) {
 
-       const [rawK, rawV] = p.split('=');
 
-       const k = decodeURIComponent((rawK || '').trim()).toLowerCase();
 
-       const v = decodeURIComponent((rawV || '').trim());
 
-       if (k === 'download' && (v === '' || v === 'true' || v === '1')) {
 
-         return true;
 
-       }
 
-     }
 
-     return false;
 
-   }
 
-   /**
 
-    * Determine if an avatar request is authorized
 
-    * Rules:
 
-    *  - If a boardId query is provided and that board is public -> allow
 
-    *  - Else if requester is authenticated (valid token) -> allow
 
-    *  - Else if avatar's owner belongs to at least one public board -> allow
 
-    *  - Otherwise -> deny
 
-    */
 
-   function isAuthorizedForAvatar(req, avatar) {
 
-     try {
 
-       if (!avatar) return false;
 
-       // 1) Check explicit board context via query
 
-       const q = parseQuery(req);
 
-       const boardId = q.boardId || q.board || q.b;
 
-       if (boardId) {
 
-         const board = ReactiveCache.getBoard(boardId);
 
-         if (board && board.isPublic && board.isPublic()) return true;
 
-         // If private board is specified, require membership of requester
 
-         const token = extractLoginToken(req);
 
-         const user = token ? getUserFromToken(token) : null;
 
-         if (user && board && board.hasMember && board.hasMember(user._id)) return true;
 
-         return false;
 
-       }
 
-       // 2) Authenticated request without explicit board context
 
-       const token = extractLoginToken(req);
 
-       const user = token ? getUserFromToken(token) : null;
 
-       if (user) return true;
 
-       // 3) Allow if avatar owner is on any public board (so avatars are public only when on public boards)
 
-       // Use a lightweight query against Boards
 
-       const found = Boards && Boards.findOne({ permission: 'public', 'members.userId': avatar.userId }, { fields: { _id: 1 } });
 
-       return !!found;
 
-     } catch (e) {
 
-       if (process.env.DEBUG === 'true') {
 
-         console.warn('Avatar authorization check failed:', e);
 
-       }
 
-       return false;
 
-     }
 
-   }
 
-   /**
 
-    * Parse cookies from request headers into an object map
 
-    */
 
-   function parseCookies(req) {
 
-     const header = req.headers && req.headers.cookie;
 
-     const out = {};
 
-     if (!header) return out;
 
-     const parts = header.split(';');
 
-     for (const part of parts) {
 
-       const idx = part.indexOf('=');
 
-       if (idx === -1) continue;
 
-       const k = decodeURIComponent(part.slice(0, idx).trim());
 
-       const v = decodeURIComponent(part.slice(idx + 1).trim());
 
-       out[k] = v;
 
-     }
 
-     return out;
 
-   }
 
-   /**
 
-    * Get query parameters as a simple object
 
-    */
 
-   function parseQuery(req) {
 
-     const out = {};
 
-     const q = (req.url || '').split('?')[1] || '';
 
-     if (!q) return out;
 
-     const pairs = q.split('&');
 
-     for (const p of pairs) {
 
-       if (!p) continue;
 
-       const [rawK, rawV] = p.split('=');
 
-       const k = decodeURIComponent((rawK || '').trim());
 
-       const v = decodeURIComponent((rawV || '').trim());
 
-       if (k) out[k] = v;
 
-     }
 
-     return out;
 
-   }
 
-   /**
 
-    * Extract a login token from Authorization header, query param, or cookie
 
-    * Supported sources (priority order):
 
-    * - Authorization: Bearer <token>
 
-    * - X-Auth-Token header
 
-    * - authToken query parameter
 
-    * - meteor_login_token or wekan_login_token cookie
 
-    */
 
-   function extractLoginToken(req) {
 
-     // Authorization: Bearer <token>
 
-     const authz = req.headers && (req.headers.authorization || req.headers.Authorization);
 
-     if (authz && typeof authz === 'string') {
 
-       const m = authz.match(/^Bearer\s+(.+)$/i);
 
-       if (m && m[1]) return m[1].trim();
 
-     }
 
-     // X-Auth-Token
 
-     const xAuth = req.headers && (req.headers['x-auth-token'] || req.headers['X-Auth-Token']);
 
-     if (xAuth && typeof xAuth === 'string') return xAuth.trim();
 
-     // Query parameter
 
-     const q = parseQuery(req);
 
-     if (q.authToken && typeof q.authToken === 'string') return q.authToken.trim();
 
-     // Cookies
 
-     const cookies = parseCookies(req);
 
-     if (cookies.meteor_login_token) return cookies.meteor_login_token.trim();
 
-     if (cookies.wekan_login_token) return cookies.wekan_login_token.trim();
 
-     return null;
 
-   }
 
-   /**
 
-    * Resolve a user from a raw login token string
 
-    */
 
-   function getUserFromToken(rawToken) {
 
-     try {
 
-       if (!rawToken || typeof rawToken !== 'string' || rawToken.length < 10) return null;
 
-       const hashed = Accounts._hashLoginToken(rawToken);
 
-       return Meteor.users.findOne({ 'services.resume.loginTokens.hashedToken': hashed }, { fields: { _id: 1 } });
 
-     } catch (e) {
 
-       // In case accounts-base is not available or any error occurs
 
-       if (process.env.DEBUG === 'true') {
 
-         console.warn('Token resolution error:', e);
 
-       }
 
-       return null;
 
-     }
 
-   }
 
-   /**
 
-    * Authorization helper for board-bound files
 
-    * - Public boards: allow
 
-    * - Private boards: require valid user who is a member
 
-    */
 
-   function isAuthorizedForBoard(req, board) {
 
-     try {
 
-       if (!board) return false;
 
-       if (board.isPublic && board.isPublic()) return true;
 
-       const token = extractLoginToken(req);
 
-       const user = token ? getUserFromToken(token) : null;
 
-       return !!(user && board.hasMember && board.hasMember(user._id));
 
-     } catch (e) {
 
-       if (process.env.DEBUG === 'true') {
 
-         console.warn('Authorization check failed:', e);
 
-       }
 
-       return false;
 
-     }
 
-   }
 
-   /**
 
-    * Helper function to stream file with error handling
 
-    */
 
-   function streamFile(res, readStream, fileObj) {
 
-     readStream.on('error', (error) => {
 
-       console.error('File stream error:', error);
 
-       if (!res.headersSent) {
 
-         res.writeHead(500);
 
-         res.end('Error reading file');
 
-       }
 
-     });
 
-     readStream.on('end', () => {
 
-       if (!res.headersSent) {
 
-         res.writeHead(200);
 
-       }
 
-     });
 
-     readStream.pipe(res);
 
-   }
 
-   // ============================================================================
 
-   // NEW METEOR-FILES ROUTES (URL-agnostic)
 
-   // ============================================================================
 
-   /**
 
-    * Serve attachments from new Meteor-Files structure
 
-    * Route: /cdn/storage/attachments/{fileId} or /cdn/storage/attachments/{fileId}/original/{filename}
 
-    */
 
-   WebApp.connectHandlers.use('/cdn/storage/attachments', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     try {
 
-       const fileId = extractFirstIdFromUrl(req, '/cdn/storage/attachments');
 
-       
 
-       if (!fileId) {
 
-         res.writeHead(400);
 
-         res.end('Invalid attachment file ID');
 
-         return;
 
-       }
 
-       // Get attachment from database with backward compatibility
 
-       const attachment = getAttachmentWithBackwardCompatibility(fileId);
 
-       if (!attachment) {
 
-         res.writeHead(404);
 
-         res.end('Attachment not found');
 
-         return;
 
-       }
 
-       // Check permissions
 
-       const board = ReactiveCache.getBoard(attachment.meta.boardId);
 
-       if (!board) {
 
-         res.writeHead(404);
 
-         res.end('Board not found');
 
-         return;
 
-       }
 
-       // Enforce cookie/header/query-based auth for private boards
 
-       if (!isAuthorizedForBoard(req, board)) {
 
-         res.writeHead(403);
 
-         res.end('Access denied');
 
-         return;
 
-       }
 
-       // Handle conditional requests
 
-       if (handleConditionalRequest(req, res, attachment)) {
 
-         return;
 
-       }
 
-       // Choose proper streaming based on source
 
-       let readStream;
 
-       if (attachment?.meta?.source === 'legacy') {
 
-         // Legacy CollectionFS GridFS stream
 
-         readStream = getOldAttachmentStream(fileId);
 
-       } else {
 
-         // New Meteor-Files storage
 
-         const strategy = attachmentStoreFactory.getFileStrategy(attachment, 'original');
 
-         readStream = strategy.getReadStream();
 
-       }
 
-       if (!readStream) {
 
-         res.writeHead(404);
 
-         res.end('Attachment file not found in storage');
 
-         return;
 
-       }
 
-       // Set headers and stream file
 
-       if (isDownloadRequested(req)) {
 
-         // Force download if requested via query param
 
-         res.setHeader('Cache-Control', 'public, max-age=31536000');
 
-         res.setHeader('ETag', `"${attachment._id}"`);
 
-         if (attachment.size) res.setHeader('Content-Length', attachment.size);
 
-         res.setHeader('X-Content-Type-Options', 'nosniff');
 
-         res.setHeader('Content-Type', 'application/octet-stream');
 
-         res.setHeader('Content-Disposition', `attachment; filename="${attachment.name}"`);
 
-         res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;");
 
-       } else {
 
-         setFileHeaders(res, attachment, true);
 
-       }
 
-       streamFile(res, readStream, attachment);
 
-     } catch (error) {
 
-       console.error('Attachment server error:', error);
 
-       if (!res.headersSent) {
 
-         res.writeHead(500);
 
-         res.end('Internal server error');
 
-       }
 
-     }
 
-   });
 
-   /**
 
-    * Serve avatars from new Meteor-Files structure
 
-    * Route: /cdn/storage/avatars/{fileId} or /cdn/storage/avatars/{fileId}/original/{filename}
 
-    */
 
-   WebApp.connectHandlers.use('/cdn/storage/avatars', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     try {
 
-       const fileId = extractFirstIdFromUrl(req, '/cdn/storage/avatars');
 
-       
 
-       if (!fileId) {
 
-         res.writeHead(400);
 
-         res.end('Invalid avatar file ID');
 
-         return;
 
-       }
 
-       // Get avatar from database
 
-       const avatar = ReactiveCache.getAvatar(fileId);
 
-       if (!avatar) {
 
-         res.writeHead(404);
 
-         res.end('Avatar not found');
 
-         return;
 
-       }
 
-       // Enforce visibility: avatars are public only in the context of public boards
 
-       if (!isAuthorizedForAvatar(req, avatar)) {
 
-         res.writeHead(403);
 
-         res.end('Access denied');
 
-         return;
 
-       }
 
-       // Handle conditional requests
 
-       if (handleConditionalRequest(req, res, avatar)) {
 
-         return;
 
-       }
 
-       // Get file strategy and stream
 
-   const strategy = avatarStoreFactory.getFileStrategy(avatar, 'original');
 
-       const readStream = strategy.getReadStream();
 
-       if (!readStream) {
 
-         res.writeHead(404);
 
-         res.end('Avatar file not found in storage');
 
-         return;
 
-       }
 
-       // Set headers and stream file
 
-       setFileHeaders(res, avatar, false);
 
-       streamFile(res, readStream, avatar);
 
-     } catch (error) {
 
-       console.error('Avatar server error:', error);
 
-       if (!res.headersSent) {
 
-         res.writeHead(500);
 
-         res.end('Internal server error');
 
-       }
 
-     }
 
-   });
 
-   // ============================================================================
 
-   // LEGACY COLLECTIONFS ROUTES (Backward compatibility)
 
-   // ============================================================================
 
-   /**
 
-    * Serve legacy attachments from CollectionFS structure
 
-    * Route: /cfs/files/attachments/{attachmentId}
 
-    */
 
-   WebApp.connectHandlers.use('/cfs/files/attachments', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     try {
 
-       const attachmentId = extractFirstIdFromUrl(req, '/cfs/files/attachments');
 
-       
 
-       if (!attachmentId) {
 
-         res.writeHead(400);
 
-         res.end('Invalid attachment ID');
 
-         return;
 
-       }
 
-       // Try to get attachment with backward compatibility
 
-       const attachment = getAttachmentWithBackwardCompatibility(attachmentId);
 
-       if (!attachment) {
 
-         res.writeHead(404);
 
-         res.end('Attachment not found');
 
-         return;
 
-       }
 
-       // Check permissions
 
-       const board = ReactiveCache.getBoard(attachment.meta.boardId);
 
-       if (!board) {
 
-         res.writeHead(404);
 
-         res.end('Board not found');
 
-         return;
 
-       }
 
-       // Enforce cookie/header/query-based auth for private boards
 
-       if (!isAuthorizedForBoard(req, board)) {
 
-         res.writeHead(403);
 
-         res.end('Access denied');
 
-         return;
 
-       }
 
-       // Handle conditional requests
 
-       if (handleConditionalRequest(req, res, attachment)) {
 
-         return;
 
-       }
 
-       // For legacy attachments, try to get GridFS stream
 
-   const fileStream = getOldAttachmentStream(attachmentId);
 
-       if (fileStream) {
 
-         if (isDownloadRequested(req)) {
 
-           // Force download if requested
 
-           res.setHeader('Cache-Control', 'public, max-age=31536000');
 
-           res.setHeader('ETag', `"${attachment._id}"`);
 
-           if (attachment.size) res.setHeader('Content-Length', attachment.size);
 
-           res.setHeader('X-Content-Type-Options', 'nosniff');
 
-           res.setHeader('Content-Type', 'application/octet-stream');
 
-           res.setHeader('Content-Disposition', `attachment; filename="${attachment.name}"`);
 
-           res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;");
 
-         } else {
 
-           setFileHeaders(res, attachment, true);
 
-         }
 
-         streamFile(res, fileStream, attachment);
 
-       } else {
 
-         res.writeHead(404);
 
-         res.end('Legacy attachment file not found in GridFS');
 
-       }
 
-     } catch (error) {
 
-       console.error('Legacy attachment server error:', error);
 
-       if (!res.headersSent) {
 
-         res.writeHead(500);
 
-         res.end('Internal server error');
 
-       }
 
-     }
 
-   });
 
-   /**
 
-    * Serve legacy avatars from CollectionFS structure
 
-    * Route: /cfs/files/avatars/{avatarId}
 
-    */
 
-   WebApp.connectHandlers.use('/cfs/files/avatars', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     try {
 
-       const avatarId = extractFirstIdFromUrl(req, '/cfs/files/avatars');
 
-       
 
-       if (!avatarId) {
 
-         res.writeHead(400);
 
-         res.end('Invalid avatar ID');
 
-         return;
 
-       }
 
-       // Try to get avatar from database (new structure first)
 
-       let avatar = ReactiveCache.getAvatar(avatarId);
 
-       
 
-       // If not found in new structure, try to handle legacy format
 
-       if (!avatar) {
 
-         // For legacy avatars, we might need to handle different ID formats
 
-         // This is a fallback for old CollectionFS avatars
 
-         res.writeHead(404);
 
-         res.end('Avatar not found');
 
-         return;
 
-       }
 
-       // Enforce visibility for legacy avatars as well
 
-       if (!isAuthorizedForAvatar(req, avatar)) {
 
-         res.writeHead(403);
 
-         res.end('Access denied');
 
-         return;
 
-       }
 
-       // Handle conditional requests
 
-       if (handleConditionalRequest(req, res, avatar)) {
 
-         return;
 
-       }
 
-       // Get file strategy and stream
 
-   const strategy = avatarStoreFactory.getFileStrategy(avatar, 'original');
 
-       const readStream = strategy.getReadStream();
 
-       if (!readStream) {
 
-         res.writeHead(404);
 
-         res.end('Avatar file not found in storage');
 
-         return;
 
-       }
 
-       // Set headers and stream file
 
-       setFileHeaders(res, avatar, false);
 
-       streamFile(res, readStream, avatar);
 
-     } catch (error) {
 
-       console.error('Legacy avatar server error:', error);
 
-       if (!res.headersSent) {
 
-         res.writeHead(500);
 
-         res.end('Internal server error');
 
-       }
 
-     }
 
-   });
 
-   // ============================================================================
 
-   // ALTERNATIVE ROUTES (For different URL patterns)
 
-   // ============================================================================
 
-   /**
 
-    * Alternative attachment route for different URL patterns
 
-    * Route: /attachments/{fileId}
 
-    */
 
-   WebApp.connectHandlers.use('/attachments', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     // Redirect to standard route
 
-     const fileId = extractFirstIdFromUrl(req, '/attachments');
 
-     const newUrl = `/cdn/storage/attachments/${fileId}`;
 
-     res.writeHead(301, { 'Location': newUrl });
 
-     res.end();
 
-   });
 
-   /**
 
-    * Alternative avatar route for different URL patterns
 
-    * Route: /avatars/{fileId}
 
-    */
 
-   WebApp.connectHandlers.use('/avatars', (req, res, next) => {
 
-     if (req.method !== 'GET') {
 
-       return next();
 
-     }
 
-     // Redirect to standard route
 
-     const fileId = extractFirstIdFromUrl(req, '/avatars');
 
-     const newUrl = `/cdn/storage/avatars/${fileId}`;
 
-     res.writeHead(301, { 'Location': newUrl });
 
-     res.end();
 
-   });
 
-   console.log('Universal file server initialized successfully');
 
- }
 
 
  |