@@ -246,29 +246,37 @@ func (s *Service) UploadPostFile(ctx *gin.Context) {
246246 objectName := "posts/" + blogID + "/" + fname
247247 contentType := fileHeader .Header .Get ("Content-Type" )
248248
249- // Read into memory once so we can compute image metadata (BlurHash) and upload
250- data , err := io .ReadAll (file )
251- if err != nil {
252- ctx .AbortWithStatusJSON (http .StatusBadRequest , gin.H {"message" : "read failed" })
253- return
254- }
255- reader := bytes .NewReader (data )
256-
257249 opts := minio.PutObjectOptions {
258250 ContentType : contentType ,
259- CacheControl : "public, max-age=31536000, immutable " ,
251+ CacheControl : "public, max-age=31536000" ,
260252 }
261- if strings .HasPrefix (strings .ToLower (contentType ), "image/" ) {
262- if hash , w , h , ok := s .computeImageMetadata (contentType , data ); ok {
263- opts .UserMetadata = map [string ]string {
264- "x-blurhash" : hash ,
265- "x-width" : strconv .Itoa (w ),
266- "x-height" : strconv .Itoa (h ),
253+
254+ // Read a small prefix for image metadata if it's an image
255+ // 5MB limit for metadata processing to keep memory low
256+ const metadataLimit = 5 * 1024 * 1024
257+ var finalReader io.Reader = file
258+ var objectSize int64 = fileHeader .Size
259+
260+ if strings .HasPrefix (strings .ToLower (contentType ), "image/" ) && fileHeader .Size <= metadataLimit {
261+ // Read into memory ONLY if small enough for metadata
262+ data , err := io .ReadAll (file )
263+ if err == nil {
264+ if hash , w , h , ok := s .computeImageMetadata (contentType , data ); ok {
265+ opts .UserMetadata = map [string ]string {
266+ "x-blurhash" : hash ,
267+ "x-width" : strconv .Itoa (w ),
268+ "x-height" : strconv .Itoa (h ),
269+ }
267270 }
271+ finalReader = bytes .NewReader (data )
272+ objectSize = int64 (len (data ))
273+ } else {
274+ // Fallback to streaming if read fails
275+ _ , _ = file .Seek (0 , io .SeekStart )
268276 }
269277 }
270278
271- info , err := s .mc .PutObject (ctx .Request .Context (), s .bucket , objectName , reader , int64 ( len ( data )) , opts )
279+ info , err := s .mc .PutObject (ctx .Request .Context (), s .bucket , objectName , finalReader , objectSize , opts )
272280 if err != nil {
273281 s .log .Errorf ("minio PutObject error: %v" , err )
274282 ctx .AbortWithStatusJSON (http .StatusInternalServerError , gin.H {"message" : "upload failed" })
@@ -396,7 +404,7 @@ func (s *Service) UpdatePostFile(ctx *gin.Context) {
396404
397405 opts := minio.PutObjectOptions {
398406 ContentType : contentType ,
399- CacheControl : "public, max-age=31536000, immutable " ,
407+ CacheControl : "public, max-age=31536000" ,
400408 }
401409 if strings .HasPrefix (strings .ToLower (contentType ), "image/" ) {
402410 if hash , w , h , ok := s .computeImageMetadata (contentType , data ); ok {
@@ -473,6 +481,11 @@ func (s *Service) GetPostFile(ctx *gin.Context) {
473481 }
474482 }
475483
484+ // For PDFs, ensure inline display in iframes
485+ if strings .Contains (strings .ToLower (stat .ContentType ), "application/pdf" ) {
486+ ctx .Header ("Content-Disposition" , "inline" )
487+ }
488+
476489 // Stream body
477490 if _ , err := io .Copy (ctx .Writer , obj ); err != nil {
478491 s .log .Errorf ("stream write error: %v" , err )
@@ -535,28 +548,34 @@ func (s *Service) UploadProfileImage(ctx *gin.Context) {
535548 objectName := "profiles/" + userID + "/profile" // single canonical key for profile image
536549 contentType := fileHeader .Header .Get ("Content-Type" )
537550
538- data , err := io .ReadAll (file )
539- if err != nil {
540- ctx .AbortWithStatusJSON (http .StatusBadRequest , gin.H {"message" : "read failed" })
541- return
542- }
543- reader := bytes .NewReader (data )
551+ // Streaming upload
552+ const metadataLimit = 5 * 1024 * 1024
553+ var finalReader io.Reader = file
554+ var objectSize int64 = fileHeader .Size
544555
545556 opts := minio.PutObjectOptions {
546557 ContentType : contentType ,
547558 CacheControl : "public, max-age=31536000, immutable" ,
548559 }
549- if strings .HasPrefix (strings .ToLower (contentType ), "image/" ) {
550- if hash , w , h , ok := s .computeImageMetadata (contentType , data ); ok {
551- opts .UserMetadata = map [string ]string {
552- "x-blurhash" : hash ,
553- "x-width" : strconv .Itoa (w ),
554- "x-height" : strconv .Itoa (h ),
560+
561+ if strings .HasPrefix (strings .ToLower (contentType ), "image/" ) && fileHeader .Size <= metadataLimit {
562+ data , err := io .ReadAll (file )
563+ if err == nil {
564+ if hash , w , h , ok := s .computeImageMetadata (contentType , data ); ok {
565+ opts .UserMetadata = map [string ]string {
566+ "x-blurhash" : hash ,
567+ "x-width" : strconv .Itoa (w ),
568+ "x-height" : strconv .Itoa (h ),
569+ }
555570 }
571+ finalReader = bytes .NewReader (data )
572+ objectSize = int64 (len (data ))
573+ } else {
574+ _ , _ = file .Seek (0 , io .SeekStart )
556575 }
557576 }
558577
559- info , err := s .mc .PutObject (ctx .Request .Context (), s .bucket , objectName , reader , int64 ( len ( data )) , opts )
578+ info , err := s .mc .PutObject (ctx .Request .Context (), s .bucket , objectName , finalReader , objectSize , opts )
560579 if err != nil {
561580 s .log .Errorf ("minio PutObject error: %v" , err )
562581 ctx .AbortWithStatusJSON (http .StatusInternalServerError , gin.H {"message" : "upload failed" })
@@ -789,6 +808,12 @@ func (s *Service) GetPostFileURL(ctx *gin.Context) {
789808 }
790809 }
791810
811+ // Check existence first
812+ if _ , err := s .mc .StatObject (ctx .Request .Context (), s .bucket , objectName , minio.StatObjectOptions {}); err != nil {
813+ ctx .AbortWithStatusJSON (http .StatusNotFound , gin.H {"message" : "file not found" })
814+ return
815+ }
816+
792817 urlStr , err := s .presignedOrCDNURL (ctx .Request .Context (), objectName , time .Duration (expires )* time .Second )
793818 if err != nil {
794819 ctx .AbortWithStatusJSON (http .StatusInternalServerError , gin.H {"message" : "could not generate url" })
@@ -817,6 +842,12 @@ func (s *Service) GetProfileURL(ctx *gin.Context) {
817842 }
818843 }
819844
845+ // Check existence first
846+ if _ , err := s .mc .StatObject (ctx .Request .Context (), s .bucket , objectName , minio.StatObjectOptions {}); err != nil {
847+ ctx .AbortWithStatusJSON (http .StatusNotFound , gin.H {"message" : "profile not found" })
848+ return
849+ }
850+
820851 urlStr , err := s .presignedOrCDNURL (ctx .Request .Context (), objectName , time .Duration (expires )* time .Second )
821852 if err != nil {
822853 ctx .AbortWithStatusJSON (http .StatusInternalServerError , gin.H {"message" : "could not generate url" })
0 commit comments