Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
# Large Data Directories (Must be excluded from build context)
blogs/
profile/
local/
node_modules/
local_blogs/
local_profiles/
backup/
Expand Down
2 changes: 1 addition & 1 deletion contribution/contribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 6 additions & 4 deletions documents/api_usage_report.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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`. |

---

Expand All @@ -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 |
Expand Down
14 changes: 9 additions & 5 deletions microservices/the_monkeys_authz/internal/utils/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func init() {
func ResetPasswordTemplate(firstName, lastName, secret string, username string) string {
return `<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
Expand Down Expand Up @@ -121,11 +122,14 @@ func ResetPasswordTemplate(firstName, lastName, secret string, username string)
</a>

<p style="color: #ed3232">
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.
</p>

<p>
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.
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.
</p>

<p>We always welcome to the community,</p>
Expand All @@ -146,8 +150,8 @@ func ResetPasswordTemplate(firstName, lastName, secret string, username string)
</footer>
</main>
</body>
</html>
`

</html>`
}

func EmailVerificationHTML(firstName, lastName, username, secret string) string {
Expand Down Expand Up @@ -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 <span style="font-weight: bold">The Monkeys</span>. If you
have any trouble verifying your email, please feel free to
contact our support team at <b>mail.themonkeys.life@gmail.com</b>. We're happy
contact our support team at <b>monkeys.admin@monkeys.com.co</b>. We're happy
to help.
</p>

Expand Down
2 changes: 1 addition & 1 deletion microservices/the_monkeys_gateway/internal/blog/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
45 changes: 28 additions & 17 deletions microservices/the_monkeys_gateway/internal/storage_v2/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"})
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions microservices/the_monkeys_gateway/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions microservices/the_monkeys_gateway/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"net/http"
"regexp"
"strings"

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion test_data/test_files/email_verification.html
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ <h1 style="margin-bottom: 100px; cursor: default">
Once you verify your email, you'll be ready to dive in and start
using <span style="font-weight: bold">The Monkeys</span>. If you
have any trouble verifying your email, please feel free to
contact our support team at <b>mail.themonkeys.life@gmail.com</b>. We're happy
contact our support team at <b>monkeys.admin@monkeys.com.co</b>. We're happy
to help.
</p>

Expand Down
11 changes: 8 additions & 3 deletions test_data/test_files/reset_password.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
Expand Down Expand Up @@ -101,11 +102,14 @@ <h1 style="margin-bottom: 100px; cursor: default">
</a>

<p style="color: #ed3232">
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.
</p>

<p>
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.
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.
</p>

<p>We always welcome to the community,</p>
Expand All @@ -126,4 +130,5 @@ <h1 style="margin-bottom: 100px; cursor: default">
</footer>
</main>
</body>
</html>

</html>
Loading