Skip to content

Commit 30ea38e

Browse files
authored
Feature/media handling overhaul (#352)
* feat: backend streaming support and storage v2 optimizations * feat: harden storage v2 and improve profile placeholder visibility * fix(gateway): increase timeouts and relax cache headers for large file uploads and profile pictures * feat(legal): premium legal 2.0 design overhaul, human-centric content, and support email update * fix(authz): finalize ResetPasswordTemplate formatting to match test expectations (remove trailing newline)
1 parent b7a3422 commit 30ea38e

File tree

10 files changed

+76
-38
lines changed

10 files changed

+76
-38
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
# Large Data Directories (Must be excluded from build context)
1111
blogs/
1212
profile/
13+
local/
14+
node_modules/
1315
local_blogs/
1416
local_profiles/
1517
backup/

contribution/contribution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ We're glad you're thinking about contributing to The Monkeys. If you think somet
44

55
## Reporting Issues
66

7-
If you find any issue or bug, please create a Github issue or mail us at mail.themonkeys.life@gmail.com.
7+
If you find any issue or bug, please create a Github issue or mail us at monkeys.admin@monkeys.com.co.
88

99
## Submitting Pull Requests
1010

documents/api_usage_report.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Definitive API Integration Status Report (Highly Detailed)
22

3-
**Date**: 2026-01-25 07:55 PM
3+
**Date**: 2026-01-30 08:30 AM
44
**Status**: 100% Manually Traced & Verified (Updated with Storage V2)
55
**Scope**: All 9 Gateway Internal Services (Auth, User, Blog, Notification, Storage v1/v2, AI, Admin, Systems, Activity)
66

@@ -39,11 +39,13 @@ These APIs are actively used in the frontend codebase. They are grouped by their
3939
*The frontend has successfully migrated core asset handling to Storage V2.*
4040
| Version | Method | Endpoint Path | Description | Integration Detail |
4141
|:---:|:---:|:---|:---|:---|
42-
| **V2** | **POST** | `/storage/posts/:id` | Blog asset upload | Integrated in EditorJS (Images, Videos, PDFs). |
42+
| **V2** | **POST** | `/storage/posts/:id` | Blog asset upload | Integrated in EditorJS (Images, Videos, PDFs). Supports large streams. |
4343
| **V2** | **GET** | `/storage/posts/:id/:file/url` | Asset delivery | Used for dynamic resolution in Editor and Reader. |
44+
| **V2** | **GET** | `/storage/posts/:id/:file/meta` | Deep metadata | **Active** in `BlogImage` for BlurHash/Dimensions/ETag. |
4445
| **V2** | **DELETE** | `/storage/posts/:id/:file` | Asset cleanup | **Automatic deletion hook** on block removal. |
45-
| **V2** | **POST** | `/storage/profiles/:id/profile`| Profile upload | Migrated in `UpdateProfileDialog`. |
46+
| **V2** | **POST** | `/storage/profiles/:id/profile`| Profile upload | Migrated in `UpdateProfileDialog`. Uses `must-revalidate`. |
4647
| **V2** | **GET** | `/storage/profiles/:id/profile/url`| Avatar delivery | Migrated in `useProfileImage` hook. |
48+
| **V2** | **GET** | `/storage/profiles/:id/profile/meta`| Profile metadata | **Active** in `useProfileImage` for ETag cache busting. |
4749
| **V2** | **DELETE** | `/storage/profiles/:id/profile`| Profile delete | **Active** in `UpdateProfileDialog` (Confirmation UI). |
4850

4951
### 🟢 User & Profile Service (V1)
@@ -74,6 +76,7 @@ These APIs are actively used in the frontend codebase. They are grouped by their
7476
| **V1** | **WS** | `/notification/ws-notification` | **Global Events** | Real-time stream confirmed in `WSNotificationDropdown`. |
7577
| **V1** | **GET** | `/notification/notifications` | Feed fetch | Manual inbox retrieval for the library inbox. |
7678
| **V1** | **POST** | `/contact` | Lead generation | Confirmed in the Public Contact Us support form. |
79+
| **EXT** | **GET** | `https://api.ipify.org` | Public IP lookup | Replaced `public-ip` library with direct fetch in `clientInfo.ts`. |
7780

7881
---
7982

@@ -83,7 +86,6 @@ These APIs represent implemented backend features that are not yet exposed or ut
8386

8487
| **V2** | **HEAD** | `/storage/posts/:id/:file` | Asset metadata | Pending Frontend Migration. |
8588
| **V2** | **LIST** | `/storage/posts/:id` | Folder listing | Pending Frontend Migration. |
86-
| **V2** | **GET** | `/storage/posts/:id/:fileName/meta` | Deep metadata | Pending Metadata rendering (Blurhash/Dimensions). |
8789

8890
### 🔴 Secure Administrative Tools (Admin Service)
8991
| Version | Method | Endpoint Path | Description | Status |

microservices/the_monkeys_authz/internal/utils/html.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func init() {
2020
func ResetPasswordTemplate(firstName, lastName, secret string, username string) string {
2121
return `<!DOCTYPE html>
2222
<html lang="en">
23+
2324
<head>
2425
<meta charset="UTF-8" />
2526
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -121,11 +122,14 @@ func ResetPasswordTemplate(firstName, lastName, secret string, username string)
121122
</a>
122123
123124
<p style="color: #ed3232">
124-
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.
125+
This link will expire in 1 hours for your security. If you don't reset your password within that time, you
126+
can request a new link anytime.
125127
</p>
126128
127129
<p>
128-
Once your password is reset, you can dive in and start using <span style="font-weight: bold">The Monkeys</span> again. If you have any trouble verifying your email, please feel free to contact our support team at <b>[email protected]</b>. We're happy to help.
130+
Once your password is reset, you can dive in and start using <span style="font-weight: bold">The
131+
Monkeys</span> again. If you have any trouble verifying your email, please feel free to contact our
132+
support team at <b>[email protected]</b>. We're happy to help.
129133
</p>
130134
131135
<p>We always welcome to the community,</p>
@@ -146,8 +150,8 @@ func ResetPasswordTemplate(firstName, lastName, secret string, username string)
146150
</footer>
147151
</main>
148152
</body>
149-
</html>
150-
`
153+
154+
</html>`
151155
}
152156

153157
func EmailVerificationHTML(firstName, lastName, username, secret string) string {
@@ -315,7 +319,7 @@ func EmailVerificationHTML(firstName, lastName, username, secret string) string
315319
Once you verify your email, you'll be ready to dive in and start
316320
using <span style="font-weight: bold">The Monkeys</span>. If you
317321
have any trouble verifying your email, please feel free to
318-
contact our support team at <b>mail.themonkeys.life@gmail.com</b>. We're happy
322+
contact our support team at <b>monkeys.admin@monkeys.com.co</b>. We're happy
319323
to help.
320324
</p>
321325

microservices/the_monkeys_gateway/internal/blog/routes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ func (asc *BlogServiceClient) WriteBlog(ctx *gin.Context) {
741741
}
742742

743743
// Set connection timeouts and limits
744-
conn.SetReadLimit(1024 * 1024) // 1MB max message size
744+
conn.SetReadLimit(int64(constants.MaxMsgSize)) // Support up to 20MB messages
745745
conn.SetReadDeadline(time.Now().Add(70 * time.Second)) // Slightly longer than client heartbeat
746746
conn.SetPongHandler(func(string) error {
747747
asc.log.Debug("Received pong from client")

microservices/the_monkeys_gateway/internal/storage_v2/routes.go

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ func (s *Service) UploadPostFile(ctx *gin.Context) {
274274
// Fallback to streaming if read fails
275275
_, _ = file.Seek(0, io.SeekStart)
276276
}
277+
} else {
278+
// For videos or large images, we MUST ensure we are at the start
279+
_, _ = file.Seek(0, io.SeekStart)
277280
}
278281

279282
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) {
394397
defer file.Close()
395398

396399
contentType := fileHeader.Header.Get("Content-Type")
397-
398-
data, err := io.ReadAll(file)
399-
if err != nil {
400-
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": "read failed"})
401-
return
402-
}
403-
reader := bytes.NewReader(data)
404-
405400
opts := minio.PutObjectOptions{
406401
ContentType: contentType,
407402
CacheControl: "public, max-age=31536000",
408403
}
409-
if strings.HasPrefix(strings.ToLower(contentType), "image/") {
410-
if hash, w, h, ok := s.computeImageMetadata(contentType, data); ok {
411-
opts.UserMetadata = map[string]string{
412-
"x-blurhash": hash,
413-
"x-width": strconv.Itoa(w),
414-
"x-height": strconv.Itoa(h),
404+
405+
const metadataLimit = 5 * 1024 * 1024
406+
var finalReader io.Reader = file
407+
var objectSize int64 = fileHeader.Size
408+
409+
if strings.HasPrefix(strings.ToLower(contentType), "image/") && fileHeader.Size <= metadataLimit {
410+
data, err := io.ReadAll(file)
411+
if err == nil {
412+
if hash, w, h, ok := s.computeImageMetadata(contentType, data); ok {
413+
opts.UserMetadata = map[string]string{
414+
"x-blurhash": hash,
415+
"x-width": strconv.Itoa(w),
416+
"x-height": strconv.Itoa(h),
417+
}
415418
}
419+
finalReader = bytes.NewReader(data)
420+
objectSize = int64(len(data))
421+
} else {
422+
_, _ = file.Seek(0, io.SeekStart)
416423
}
424+
} else {
425+
_, _ = file.Seek(0, io.SeekStart)
417426
}
418427

419-
info, err := s.mc.PutObject(ctx.Request.Context(), s.bucket, objectName, reader, int64(len(data)), opts)
428+
info, err := s.mc.PutObject(ctx.Request.Context(), s.bucket, objectName, finalReader, objectSize, opts)
420429
if err != nil {
421430
s.log.Errorf("minio PutObject (update) error: %v", err)
422431
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "update failed"})
@@ -555,7 +564,7 @@ func (s *Service) UploadProfileImage(ctx *gin.Context) {
555564

556565
opts := minio.PutObjectOptions{
557566
ContentType: contentType,
558-
CacheControl: "public, max-age=31536000, immutable",
567+
CacheControl: "public, max-age=3600, must-revalidate",
559568
}
560569

561570
if strings.HasPrefix(strings.ToLower(contentType), "image/") && fileHeader.Size <= metadataLimit {
@@ -573,6 +582,8 @@ func (s *Service) UploadProfileImage(ctx *gin.Context) {
573582
} else {
574583
_, _ = file.Seek(0, io.SeekStart)
575584
}
585+
} else {
586+
_, _ = file.Seek(0, io.SeekStart)
576587
}
577588

578589
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) {
664675

665676
opts := minio.PutObjectOptions{
666677
ContentType: contentType,
667-
CacheControl: "public, max-age=31536000, immutable",
678+
CacheControl: "public, max-age=3600, must-revalidate",
668679
}
669680
if strings.HasPrefix(strings.ToLower(contentType), "image/") {
670681
if hash, w, h, ok := s.computeImageMetadata(contentType, data); ok {

microservices/the_monkeys_gateway/main.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ func (s *Server) launchServer(ctx context.Context, cfg *config.Config, tlsCert,
147147
httpSrv := &http.Server{
148148
Addr: httpAddr,
149149
Handler: s.router,
150-
MaxHeaderBytes: 1 << 20,
151-
ReadTimeout: 10 * time.Second,
152-
WriteTimeout: 10 * time.Second,
150+
MaxHeaderBytes: 5 << 20,
151+
ReadTimeout: 1 * time.Hour,
152+
WriteTimeout: 1 * time.Hour,
153153
}
154154

155155
// HTTPS server (with TLS)
@@ -160,9 +160,9 @@ func (s *Server) launchServer(ctx context.Context, cfg *config.Config, tlsCert,
160160
httpsSrv := &http.Server{
161161
Addr: httpsAddr,
162162
Handler: s.router,
163-
MaxHeaderBytes: 1 << 20,
164-
ReadTimeout: 10 * time.Second,
165-
WriteTimeout: 10 * time.Second,
163+
MaxHeaderBytes: 5 << 20,
164+
ReadTimeout: 1 * time.Hour,
165+
WriteTimeout: 1 * time.Hour,
166166
}
167167

168168
// Start the HTTP server in a background goroutine

microservices/the_monkeys_gateway/middleware/middleware.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"net/http"
77
"regexp"
8+
"strings"
89

910
"github.com/gin-contrib/cors"
1011
"github.com/gin-gonic/gin"
@@ -62,6 +63,19 @@ func NewCorsMiddleware() gin.HandlerFunc {
6263
func LogRequestBody() gin.HandlerFunc {
6364
lg := zap.S().With("middleware", "req_body")
6465
return func(c *gin.Context) {
66+
// Skip logging for multipart form data (large files/videos)
67+
contentType := c.Request.Header.Get("Content-Type")
68+
if strings.Contains(contentType, "multipart/form-data") {
69+
c.Next()
70+
return
71+
}
72+
73+
// Skip logging if Content-Length is too large (> 1MB)
74+
if c.Request.ContentLength > 1024*1024 {
75+
c.Next()
76+
return
77+
}
78+
6579
var bodyBuffer bytes.Buffer
6680
if _, err := io.Copy(&bodyBuffer, c.Request.Body); err != nil {
6781
lg.Errorw("copy body failed", "err", err)

test_data/test_files/email_verification.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ <h1 style="margin-bottom: 100px; cursor: default">
162162
Once you verify your email, you'll be ready to dive in and start
163163
using <span style="font-weight: bold">The Monkeys</span>. If you
164164
have any trouble verifying your email, please feel free to
165-
contact our support team at <b>mail.themonkeys.life@gmail.com</b>. We're happy
165+
contact our support team at <b>monkeys.admin@monkeys.com.co</b>. We're happy
166166
to help.
167167
</p>
168168

test_data/test_files/reset_password.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<!DOCTYPE html>
22
<html lang="en">
3+
34
<head>
45
<meta charset="UTF-8" />
56
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -101,11 +102,14 @@ <h1 style="margin-bottom: 100px; cursor: default">
101102
</a>
102103

103104
<p style="color: #ed3232">
104-
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.
105+
This link will expire in 1 hours for your security. If you don't reset your password within that time, you
106+
can request a new link anytime.
105107
</p>
106108

107109
<p>
108-
Once your password is reset, you can dive in and start using <span style="font-weight: bold">The Monkeys</span> again. If you have any trouble verifying your email, please feel free to contact our support team at <b>[email protected]</b>. We're happy to help.
110+
Once your password is reset, you can dive in and start using <span style="font-weight: bold">The
111+
Monkeys</span> again. If you have any trouble verifying your email, please feel free to contact our
112+
support team at <b>[email protected]</b>. We're happy to help.
109113
</p>
110114

111115
<p>We always welcome to the community,</p>
@@ -126,4 +130,5 @@ <h1 style="margin-bottom: 100px; cursor: default">
126130
</footer>
127131
</main>
128132
</body>
129-
</html>
133+
134+
</html>

0 commit comments

Comments
 (0)