avatarServer.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /**
  2. * Avatar File Server
  3. * Handles serving avatar files from the /cdn/storage/avatars/ path
  4. */
  5. import { Meteor } from 'meteor/meteor';
  6. import { WebApp } from 'meteor/webapp';
  7. import { ReactiveCache } from '/imports/reactiveCache';
  8. import Avatars from '/models/avatars';
  9. import { fileStoreStrategyFactory } from '/models/lib/fileStoreStrategy';
  10. import fs from 'fs';
  11. import path from 'path';
  12. if (Meteor.isServer) {
  13. // Handle avatar file downloads
  14. WebApp.connectHandlers.use('/cdn/storage/avatars/([^/]+)', (req, res, next) => {
  15. if (req.method !== 'GET') {
  16. return next();
  17. }
  18. try {
  19. const fileName = req.params[0];
  20. if (!fileName) {
  21. res.writeHead(400);
  22. res.end('Invalid avatar file name');
  23. return;
  24. }
  25. // Extract file ID from filename (format: fileId-original-filename)
  26. const fileId = fileName.split('-original-')[0];
  27. if (!fileId) {
  28. res.writeHead(400);
  29. res.end('Invalid avatar file format');
  30. return;
  31. }
  32. // Get avatar file from database
  33. const avatar = ReactiveCache.getAvatar(fileId);
  34. if (!avatar) {
  35. res.writeHead(404);
  36. res.end('Avatar not found');
  37. return;
  38. }
  39. // Check if user has permission to view this avatar
  40. // For avatars, we allow viewing by any logged-in user
  41. const userId = Meteor.userId();
  42. if (!userId) {
  43. res.writeHead(401);
  44. res.end('Authentication required');
  45. return;
  46. }
  47. // Get file strategy
  48. const strategy = fileStoreStrategyFactory.getFileStrategy(avatar, 'original');
  49. const readStream = strategy.getReadStream();
  50. if (!readStream) {
  51. res.writeHead(404);
  52. res.end('Avatar file not found in storage');
  53. return;
  54. }
  55. // Set appropriate headers
  56. res.setHeader('Content-Type', avatar.type || 'image/jpeg');
  57. res.setHeader('Content-Length', avatar.size || 0);
  58. res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for 1 year
  59. res.setHeader('ETag', `"${avatar._id}"`);
  60. // Handle conditional requests
  61. const ifNoneMatch = req.headers['if-none-match'];
  62. if (ifNoneMatch && ifNoneMatch === `"${avatar._id}"`) {
  63. res.writeHead(304);
  64. res.end();
  65. return;
  66. }
  67. // Stream the file
  68. res.writeHead(200);
  69. readStream.pipe(res);
  70. readStream.on('error', (error) => {
  71. console.error('Avatar stream error:', error);
  72. if (!res.headersSent) {
  73. res.writeHead(500);
  74. res.end('Error reading avatar file');
  75. }
  76. });
  77. } catch (error) {
  78. console.error('Avatar server error:', error);
  79. if (!res.headersSent) {
  80. res.writeHead(500);
  81. res.end('Internal server error');
  82. }
  83. }
  84. });
  85. // Handle legacy avatar URLs (from CollectionFS)
  86. WebApp.connectHandlers.use('/cfs/files/avatars/([^/]+)', (req, res, next) => {
  87. if (req.method !== 'GET') {
  88. return next();
  89. }
  90. try {
  91. const fileName = req.params[0];
  92. // Redirect to new avatar URL format
  93. const newUrl = `/cdn/storage/avatars/${fileName}`;
  94. res.writeHead(301, { 'Location': newUrl });
  95. res.end();
  96. } catch (error) {
  97. console.error('Legacy avatar redirect error:', error);
  98. res.writeHead(500);
  99. res.end('Internal server error');
  100. }
  101. });
  102. console.log('Avatar server routes initialized');
  103. }