diff --git a/.dockerignore b/.dockerignore index 9d34fde5..e21b16b9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,6 +10,8 @@ # Large Data Directories (Must be excluded from build context) blogs/ profile/ +local/ +node_modules/ local_blogs/ local_profiles/ backup/ diff --git a/contribution/contribution.md b/contribution/contribution.md index 9b981ecb..7a925f61 100644 --- a/contribution/contribution.md +++ b/contribution/contribution.md @@ -4,7 +4,7 @@ We're glad you're thinking about contributing to The Monkeys. If you think somet ## Reporting Issues -If you find any issue or bug, please create a Github issue or mail us at mail.themonkeys.life@gmail.com. +If you find any issue or bug, please create a Github issue or mail us at monkeys.admin@monkeys.com.co. ## Submitting Pull Requests diff --git a/documents/api_usage_report.md b/documents/api_usage_report.md index 451b629e..f54a5b6a 100644 --- a/documents/api_usage_report.md +++ b/documents/api_usage_report.md @@ -1,6 +1,6 @@ # Definitive API Integration Status Report (Highly Detailed) -**Date**: 2026-01-25 07:55 PM +**Date**: 2026-01-30 08:30 AM **Status**: 100% Manually Traced & Verified (Updated with Storage V2) **Scope**: All 9 Gateway Internal Services (Auth, User, Blog, Notification, Storage v1/v2, AI, Admin, Systems, Activity) @@ -39,11 +39,13 @@ These APIs are actively used in the frontend codebase. They are grouped by their *The frontend has successfully migrated core asset handling to Storage V2.* | Version | Method | Endpoint Path | Description | Integration Detail | |:---:|:---:|:---|:---|:---| -| **V2** | **POST** | `/storage/posts/:id` | Blog asset upload | Integrated in EditorJS (Images, Videos, PDFs). | +| **V2** | **POST** | `/storage/posts/:id` | Blog asset upload | Integrated in EditorJS (Images, Videos, PDFs). Supports large streams. | | **V2** | **GET** | `/storage/posts/:id/:file/url` | Asset delivery | Used for dynamic resolution in Editor and Reader. | +| **V2** | **GET** | `/storage/posts/:id/:file/meta` | Deep metadata | **Active** in `BlogImage` for BlurHash/Dimensions/ETag. | | **V2** | **DELETE** | `/storage/posts/:id/:file` | Asset cleanup | **Automatic deletion hook** on block removal. | -| **V2** | **POST** | `/storage/profiles/:id/profile`| Profile upload | Migrated in `UpdateProfileDialog`. | +| **V2** | **POST** | `/storage/profiles/:id/profile`| Profile upload | Migrated in `UpdateProfileDialog`. Uses `must-revalidate`. | | **V2** | **GET** | `/storage/profiles/:id/profile/url`| Avatar delivery | Migrated in `useProfileImage` hook. | +| **V2** | **GET** | `/storage/profiles/:id/profile/meta`| Profile metadata | **Active** in `useProfileImage` for ETag cache busting. | | **V2** | **DELETE** | `/storage/profiles/:id/profile`| Profile delete | **Active** in `UpdateProfileDialog` (Confirmation UI). | ### 🟢 User & Profile Service (V1) @@ -74,6 +76,7 @@ These APIs are actively used in the frontend codebase. They are grouped by their | **V1** | **WS** | `/notification/ws-notification` | **Global Events** | Real-time stream confirmed in `WSNotificationDropdown`. | | **V1** | **GET** | `/notification/notifications` | Feed fetch | Manual inbox retrieval for the library inbox. | | **V1** | **POST** | `/contact` | Lead generation | Confirmed in the Public Contact Us support form. | +| **EXT** | **GET** | `https://api.ipify.org` | Public IP lookup | Replaced `public-ip` library with direct fetch in `clientInfo.ts`. | --- @@ -83,7 +86,6 @@ These APIs represent implemented backend features that are not yet exposed or ut | **V2** | **HEAD** | `/storage/posts/:id/:file` | Asset metadata | Pending Frontend Migration. | | **V2** | **LIST** | `/storage/posts/:id` | Folder listing | Pending Frontend Migration. | -| **V2** | **GET** | `/storage/posts/:id/:fileName/meta` | Deep metadata | Pending Metadata rendering (Blurhash/Dimensions). | ### 🔴 Secure Administrative Tools (Admin Service) | Version | Method | Endpoint Path | Description | Status | diff --git a/microservices/the_monkeys_authz/internal/utils/html.go b/microservices/the_monkeys_authz/internal/utils/html.go index 95ef8569..5d1fd54e 100644 --- a/microservices/the_monkeys_authz/internal/utils/html.go +++ b/microservices/the_monkeys_authz/internal/utils/html.go @@ -20,6 +20,7 @@ func init() { func ResetPasswordTemplate(firstName, lastName, secret string, username string) string { return ` + @@ -121,11 +122,14 @@ func ResetPasswordTemplate(firstName, lastName, secret string, username string)

- This link will expire in 1 hours for your security. If you don't reset your password within that time, you can request a new link anytime. + This link will expire in 1 hours for your security. If you don't reset your password within that time, you + can request a new link anytime.

- Once your password is reset, you can dive in and start using The Monkeys again. If you have any trouble verifying your email, please feel free to contact our support team at mail.themonkeys.life@gmail.com. We're happy to help. + Once your password is reset, you can dive in and start using The + Monkeys again. If you have any trouble verifying your email, please feel free to contact our + support team at monkeys.admin@monkeys.com.co. We're happy to help.

We always welcome to the community,

@@ -146,8 +150,8 @@ func ResetPasswordTemplate(firstName, lastName, secret string, username string) - -` + +` } func EmailVerificationHTML(firstName, lastName, username, secret string) string { @@ -315,7 +319,7 @@ func EmailVerificationHTML(firstName, lastName, username, secret string) string Once you verify your email, you'll be ready to dive in and start using The Monkeys. If you have any trouble verifying your email, please feel free to - contact our support team at mail.themonkeys.life@gmail.com. We're happy + contact our support team at monkeys.admin@monkeys.com.co. We're happy to help.

diff --git a/microservices/the_monkeys_gateway/internal/blog/routes.go b/microservices/the_monkeys_gateway/internal/blog/routes.go index 5fde8d2c..2dcc5361 100644 --- a/microservices/the_monkeys_gateway/internal/blog/routes.go +++ b/microservices/the_monkeys_gateway/internal/blog/routes.go @@ -741,7 +741,7 @@ func (asc *BlogServiceClient) WriteBlog(ctx *gin.Context) { } // Set connection timeouts and limits - conn.SetReadLimit(1024 * 1024) // 1MB max message size + conn.SetReadLimit(int64(constants.MaxMsgSize)) // Support up to 20MB messages conn.SetReadDeadline(time.Now().Add(70 * time.Second)) // Slightly longer than client heartbeat conn.SetPongHandler(func(string) error { asc.log.Debug("Received pong from client") diff --git a/microservices/the_monkeys_gateway/internal/storage_v2/routes.go b/microservices/the_monkeys_gateway/internal/storage_v2/routes.go index 809ba764..f68c4733 100644 --- a/microservices/the_monkeys_gateway/internal/storage_v2/routes.go +++ b/microservices/the_monkeys_gateway/internal/storage_v2/routes.go @@ -274,6 +274,9 @@ func (s *Service) UploadPostFile(ctx *gin.Context) { // Fallback to streaming if read fails _, _ = file.Seek(0, io.SeekStart) } + } else { + // For videos or large images, we MUST ensure we are at the start + _, _ = file.Seek(0, io.SeekStart) } info, err := s.mc.PutObject(ctx.Request.Context(), s.bucket, objectName, finalReader, objectSize, opts) @@ -394,29 +397,35 @@ func (s *Service) UpdatePostFile(ctx *gin.Context) { defer file.Close() contentType := fileHeader.Header.Get("Content-Type") - - data, err := io.ReadAll(file) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": "read failed"}) - return - } - reader := bytes.NewReader(data) - opts := minio.PutObjectOptions{ ContentType: contentType, CacheControl: "public, max-age=31536000", } - if strings.HasPrefix(strings.ToLower(contentType), "image/") { - if hash, w, h, ok := s.computeImageMetadata(contentType, data); ok { - opts.UserMetadata = map[string]string{ - "x-blurhash": hash, - "x-width": strconv.Itoa(w), - "x-height": strconv.Itoa(h), + + const metadataLimit = 5 * 1024 * 1024 + var finalReader io.Reader = file + var objectSize int64 = fileHeader.Size + + if strings.HasPrefix(strings.ToLower(contentType), "image/") && fileHeader.Size <= metadataLimit { + data, err := io.ReadAll(file) + if err == nil { + if hash, w, h, ok := s.computeImageMetadata(contentType, data); ok { + opts.UserMetadata = map[string]string{ + "x-blurhash": hash, + "x-width": strconv.Itoa(w), + "x-height": strconv.Itoa(h), + } } + finalReader = bytes.NewReader(data) + objectSize = int64(len(data)) + } else { + _, _ = file.Seek(0, io.SeekStart) } + } else { + _, _ = file.Seek(0, io.SeekStart) } - info, err := s.mc.PutObject(ctx.Request.Context(), s.bucket, objectName, reader, int64(len(data)), opts) + info, err := s.mc.PutObject(ctx.Request.Context(), s.bucket, objectName, finalReader, objectSize, opts) if err != nil { s.log.Errorf("minio PutObject (update) error: %v", err) ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "update failed"}) @@ -555,7 +564,7 @@ func (s *Service) UploadProfileImage(ctx *gin.Context) { opts := minio.PutObjectOptions{ ContentType: contentType, - CacheControl: "public, max-age=31536000, immutable", + CacheControl: "public, max-age=3600, must-revalidate", } if strings.HasPrefix(strings.ToLower(contentType), "image/") && fileHeader.Size <= metadataLimit { @@ -573,6 +582,8 @@ func (s *Service) UploadProfileImage(ctx *gin.Context) { } else { _, _ = file.Seek(0, io.SeekStart) } + } else { + _, _ = file.Seek(0, io.SeekStart) } info, err := s.mc.PutObject(ctx.Request.Context(), s.bucket, objectName, finalReader, objectSize, opts) @@ -664,7 +675,7 @@ func (s *Service) UpdateProfileImage(ctx *gin.Context) { opts := minio.PutObjectOptions{ ContentType: contentType, - CacheControl: "public, max-age=31536000, immutable", + CacheControl: "public, max-age=3600, must-revalidate", } if strings.HasPrefix(strings.ToLower(contentType), "image/") { if hash, w, h, ok := s.computeImageMetadata(contentType, data); ok { diff --git a/microservices/the_monkeys_gateway/main.go b/microservices/the_monkeys_gateway/main.go index 117fa388..6bc4eaf7 100644 --- a/microservices/the_monkeys_gateway/main.go +++ b/microservices/the_monkeys_gateway/main.go @@ -147,9 +147,9 @@ func (s *Server) launchServer(ctx context.Context, cfg *config.Config, tlsCert, httpSrv := &http.Server{ Addr: httpAddr, Handler: s.router, - MaxHeaderBytes: 1 << 20, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 5 << 20, + ReadTimeout: 1 * time.Hour, + WriteTimeout: 1 * time.Hour, } // HTTPS server (with TLS) @@ -160,9 +160,9 @@ func (s *Server) launchServer(ctx context.Context, cfg *config.Config, tlsCert, httpsSrv := &http.Server{ Addr: httpsAddr, Handler: s.router, - MaxHeaderBytes: 1 << 20, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 5 << 20, + ReadTimeout: 1 * time.Hour, + WriteTimeout: 1 * time.Hour, } // Start the HTTP server in a background goroutine diff --git a/microservices/the_monkeys_gateway/middleware/middleware.go b/microservices/the_monkeys_gateway/middleware/middleware.go index b35f07f7..fcc9eb96 100644 --- a/microservices/the_monkeys_gateway/middleware/middleware.go +++ b/microservices/the_monkeys_gateway/middleware/middleware.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "regexp" + "strings" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" @@ -62,6 +63,19 @@ func NewCorsMiddleware() gin.HandlerFunc { func LogRequestBody() gin.HandlerFunc { lg := zap.S().With("middleware", "req_body") return func(c *gin.Context) { + // Skip logging for multipart form data (large files/videos) + contentType := c.Request.Header.Get("Content-Type") + if strings.Contains(contentType, "multipart/form-data") { + c.Next() + return + } + + // Skip logging if Content-Length is too large (> 1MB) + if c.Request.ContentLength > 1024*1024 { + c.Next() + return + } + var bodyBuffer bytes.Buffer if _, err := io.Copy(&bodyBuffer, c.Request.Body); err != nil { lg.Errorw("copy body failed", "err", err) diff --git a/test_data/test_files/email_verification.html b/test_data/test_files/email_verification.html index eac0cd90..487e1447 100644 --- a/test_data/test_files/email_verification.html +++ b/test_data/test_files/email_verification.html @@ -162,7 +162,7 @@

Once you verify your email, you'll be ready to dive in and start using The Monkeys. If you have any trouble verifying your email, please feel free to - contact our support team at mail.themonkeys.life@gmail.com. We're happy + contact our support team at monkeys.admin@monkeys.com.co. We're happy to help.

diff --git a/test_data/test_files/reset_password.html b/test_data/test_files/reset_password.html index e55c1835..f8f07abb 100644 --- a/test_data/test_files/reset_password.html +++ b/test_data/test_files/reset_password.html @@ -1,5 +1,6 @@ + @@ -101,11 +102,14 @@

- This link will expire in 1 hours for your security. If you don't reset your password within that time, you can request a new link anytime. + This link will expire in 1 hours for your security. If you don't reset your password within that time, you + can request a new link anytime.

- Once your password is reset, you can dive in and start using The Monkeys again. If you have any trouble verifying your email, please feel free to contact our support team at mail.themonkeys.life@gmail.com. We're happy to help. + Once your password is reset, you can dive in and start using The + Monkeys again. If you have any trouble verifying your email, please feel free to contact our + support team at monkeys.admin@monkeys.com.co. We're happy to help.

We always welcome to the community,

@@ -126,4 +130,5 @@

- + + \ No newline at end of file