wekan/server/routes/avatarServer.js
Lauri Ojansivu 30620d0ca4
Some checks failed
Docker / build (push) Has been cancelled
Docker Image CI / build (push) Has been cancelled
Release Charts / release (push) Has been cancelled
Test suite / Meteor tests (push) Has been cancelled
Test suite / Coverage report (push) Has been cancelled
Some migrations and mobile fixes.
Thanks to xet7 !
2025-10-25 21:09:07 +03:00

123 lines
3.4 KiB
JavaScript

/**
* Avatar File Server
* Handles serving avatar files from the /cdn/storage/avatars/ path
*/
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import { ReactiveCache } from '/imports/reactiveCache';
import Avatars from '/models/avatars';
import { fileStoreStrategyFactory } from '/models/lib/fileStoreStrategy';
import fs from 'fs';
import path from 'path';
if (Meteor.isServer) {
// Handle avatar file downloads
WebApp.connectHandlers.use('/cdn/storage/avatars/([^/]+)', (req, res, next) => {
if (req.method !== 'GET') {
return next();
}
try {
const fileName = req.params[0];
if (!fileName) {
res.writeHead(400);
res.end('Invalid avatar file name');
return;
}
// Extract file ID from filename (format: fileId-original-filename)
const fileId = fileName.split('-original-')[0];
if (!fileId) {
res.writeHead(400);
res.end('Invalid avatar file format');
return;
}
// Get avatar file from database
const avatar = ReactiveCache.getAvatar(fileId);
if (!avatar) {
res.writeHead(404);
res.end('Avatar not found');
return;
}
// Check if user has permission to view this avatar
// For avatars, we allow viewing by any logged-in user
const userId = Meteor.userId();
if (!userId) {
res.writeHead(401);
res.end('Authentication required');
return;
}
// Get file strategy
const strategy = fileStoreStrategyFactory.getFileStrategy(avatar, 'original');
const readStream = strategy.getReadStream();
if (!readStream) {
res.writeHead(404);
res.end('Avatar file not found in storage');
return;
}
// Set appropriate headers
res.setHeader('Content-Type', avatar.type || 'image/jpeg');
res.setHeader('Content-Length', avatar.size || 0);
res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for 1 year
res.setHeader('ETag', `"${avatar._id}"`);
// Handle conditional requests
const ifNoneMatch = req.headers['if-none-match'];
if (ifNoneMatch && ifNoneMatch === `"${avatar._id}"`) {
res.writeHead(304);
res.end();
return;
}
// Stream the file
res.writeHead(200);
readStream.pipe(res);
readStream.on('error', (error) => {
console.error('Avatar stream error:', error);
if (!res.headersSent) {
res.writeHead(500);
res.end('Error reading avatar file');
}
});
} catch (error) {
console.error('Avatar server error:', error);
if (!res.headersSent) {
res.writeHead(500);
res.end('Internal server error');
}
}
});
// Handle legacy avatar URLs (from CollectionFS)
WebApp.connectHandlers.use('/cfs/files/avatars/([^/]+)', (req, res, next) => {
if (req.method !== 'GET') {
return next();
}
try {
const fileName = req.params[0];
// Redirect to new avatar URL format
const newUrl = `/cdn/storage/avatars/${fileName}`;
res.writeHead(301, { 'Location': newUrl });
res.end();
} catch (error) {
console.error('Legacy avatar redirect error:', error);
res.writeHead(500);
res.end('Internal server error');
}
});
console.log('Avatar server routes initialized');
}