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
106 changes: 106 additions & 0 deletions documents/api_usage_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Definitive API Integration Status Report (Highly Detailed)

**Date**: 2026-01-25 07:55 PM
**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)

---

## 1. INTEGRATED APIs (Confirmed in Frontend)

These APIs are actively used in the frontend codebase. They are grouped by their functional version (V1 vs V2) and include their specific transport method.

### 🟢 Blog & News Engine (V1 & V2)
| Version | Method | Endpoint Path | Description | Integration Detail |
|:---:|:---:|:---|:---|:---|
| **V2** | **WS** | `/blog/draft/:blog_id` | **Real-time drafting** | Used in `create` & `edit` pages for auto-save/sync. |
| **V2** | **GET** | `/blog/meta-feed` | Primary feed engine | Combined metadata stream for the home page. |
| **V2** | **GET** | `/blog/feed` | Latest updates | High-performance stream of recent blogs. |
| **V2** | **GET** | `/blog/:blog_id` | Blog document fetch | Used in individual blog view pages (`[slug]`). |
| **V2** | **GET** | `/blog/in-my-draft` | Personal draft list | Library dashboard integration (User's own drafts). |
| **V2** | **GET** | `/blog/in-my-bookmark` | Saved blogs list | Library dashboard integration (User's bookmarks). |
| **V2** | **GET** | `/blog/following` | Followed feed | Personalized stream of blogs from followed authors. |
| **V2** | **GET** | `/blog/user/:username` | Author blog list | Public profile integration (List of author's blogs). |
| **V2** | **GET** | `/blog/search` | Search engine | Global blog search with limit/offset support. |
| **V1** | **POST** | `/blog/publish/:blog_id` | Publication trigger | Final step in the creation/editing flow. |
| **V2** | **POST** | `/blog/to-draft/:blog_id` | Revert to draft | Used to pull a published blog back into drafting. |

### 🟢 Storage & Assets (V1 Legacy Stack)
*Note: The frontend is currently locked to the V1 File Service APIs.*
| Version | Method | Endpoint Path | Description | Integration Detail |
|:---:|:---:|:---|:---|:---|
| **V1** | **POST** | `/files/post/:id` | Blog image upload | EditorJS integration for inline images. |
| **V1** | **GET** | `/files/post/:id/:fileName` | Serve blog assets | Dynamic image rendering in published blogs. |
| **V1** | **POST** | `/files/profile/:id/profile` | Profile pic upload | Update profile dialog integration. |
| **V1** | **GET** | `/files/profile/:id/profile` | Serve avatar | Global layout sidebar and public profile usage. |
| **V1.1** | **GET** | `/files/profile/:id/profile` | Avatar stream | High-performance binary stream for profile images. |

### 🟢 Modern Storage Stack (V2 MinIO - Migration Active)
*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** | **GET** | `/storage/posts/:id/:file/url` | Asset delivery | Used for dynamic resolution in Editor and Reader. |
| **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** | **GET** | `/storage/profiles/:id/profile/url`| Avatar delivery | Migrated in `useProfileImage` hook. |
| **V2** | **DELETE** | `/storage/profiles/:id/profile`| Profile delete | **Active** in `UpdateProfileDialog` (Confirmation UI). |

### 🟢 User & Profile Service (V1)
| Version | Method | Endpoint Path | Description | Integration Detail |
|:---:|:---:|:---|:---|:---|
| **V1** | **GET** | `/user/public/:id` | Username profile | Fetch public data by username handle. |
| **V1** | **GET** | `/user/public/account/:acc_id` | Account ID profile | Fetch public data via internal account ID. |
| **V1** | **GET** | `/user/connection-count/:user` | Stats integration | Follower and following count for profiles. |
| **V1** | **POST** | `/user/follow/:username` | Follow action | Confirm/Toggle follower status. |
| **V1** | **POST** | `/user/unfollow/:username` | Unfollow action | Remove follower status. |
| **V1** | **PUT/PATCH** | `/user/:id` | Profile settings | Full or partial (patch) profile updates. |
| **V1** | **GET** | `/user/topics` | Topic registry | Fetch all valid tags for global exploration. |
| **V1** | **POST** | `/user/topics` | Topic creation | Author-defined topic registration. |
| **V1** | **GET** | `/user/activities/:user` | Activity log | Integrated public activity history timeline. |

### 🟢 Authentication Service (V1)
| Version | Method | Endpoint Path | Description | Integration Detail |
|:---:|:---:|:---|:---|:---|
| **V1** | **POST** | `/auth/login` | Login | Core authentication flow. |
| **V1** | **POST** | `/auth/register` | Signup | New user registration flow. |
| **V1** | **GET** | `/auth/validate-session` | Auth check | Initial app load context verification. |
| **V1** | **POST** | `/auth/refresh` | Token sync | Automatic silent refresh via Axios interceptors. |
| **V1** | **GET** | `/auth/ws-token` | WebSocket Auth | Obtaining one-time ticket for WS connections. |

### 🟢 Notification & System (V1)
| Version | Method | Endpoint Path | Description | Integration Detail |
|:---:|:---:|:---|:---|:---|
| **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. |

---

## 2. PENDING APIs (Backend-Only / Not in Frontend)

These APIs represent implemented backend features that are not yet exposed or utilized in the frontend UI.

| **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 |
|:---:|:---:|:---|:---|:---|
| **V1** | **GET** | `/admin/health` | Service status | Backend Cluster Monitoring only. |
| **V1** | **DELETE** | `/admin/users/flag` | User banning | Admin Panel implementation pending. |
| **V1** | **POST** | `/admin/backup/trigger`| DR recovery | DevOps/Internal CLI usage only. |

### 🔴 AI & Collaborative Intelligence
| Version | Method | Endpoint Path | Description | Status |
|:---:|:---:|:---|:---|:---|
| **V1** | **GET** | `/recommendations/*` | AI Suggestions | Recommendation Engine implementation pending. |

### 🔴 Advanced Blog & User Management (Waitlist)
| Version | Method | Endpoint Path | Description | Status |
|:---:|:---:|:---|:---|:---|
| **V1** | **POST** | `/user/invite/:blog_id` | Co-author invite | Collaborative writing feature pending. |
| **V1** | **POST** | `/blog/archive/:blog_id` | Soft delete | Archive UI integration pending. |
| **V2** | **GET** | `/user/active-users` | Real-time stats | Admin Dashboard feature pending. |
| **V2** | **GET** | `/blog/:blog_id/stats` | Deep analytics | Premium stats view integration pending. |
91 changes: 61 additions & 30 deletions microservices/the_monkeys_gateway/internal/storage_v2/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,29 +246,37 @@ func (s *Service) UploadPostFile(ctx *gin.Context) {
objectName := "posts/" + blogID + "/" + fname
contentType := fileHeader.Header.Get("Content-Type")

// Read into memory once so we can compute image metadata (BlurHash) and upload
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, immutable",
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),

// Read a small prefix for image metadata if it's an image
// 5MB limit for metadata processing to keep memory low
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 {
// Read into memory ONLY if small enough for metadata
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 {
// Fallback to streaming if read fails
_, _ = 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 error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "upload failed"})
Expand Down Expand Up @@ -396,7 +404,7 @@ func (s *Service) UpdatePostFile(ctx *gin.Context) {

opts := minio.PutObjectOptions{
ContentType: contentType,
CacheControl: "public, max-age=31536000, immutable",
CacheControl: "public, max-age=31536000",
}
if strings.HasPrefix(strings.ToLower(contentType), "image/") {
if hash, w, h, ok := s.computeImageMetadata(contentType, data); ok {
Expand Down Expand Up @@ -473,6 +481,11 @@ func (s *Service) GetPostFile(ctx *gin.Context) {
}
}

// For PDFs, ensure inline display in iframes
if strings.Contains(strings.ToLower(stat.ContentType), "application/pdf") {
ctx.Header("Content-Disposition", "inline")
}

// Stream body
if _, err := io.Copy(ctx.Writer, obj); err != nil {
s.log.Errorf("stream write error: %v", err)
Expand Down Expand Up @@ -535,28 +548,34 @@ func (s *Service) UploadProfileImage(ctx *gin.Context) {
objectName := "profiles/" + userID + "/profile" // single canonical key for profile image
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)
// Streaming upload
const metadataLimit = 5 * 1024 * 1024
var finalReader io.Reader = file
var objectSize int64 = fileHeader.Size

opts := minio.PutObjectOptions{
ContentType: contentType,
CacheControl: "public, max-age=31536000, immutable",
}
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),

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)
}
}

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 error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "upload failed"})
Expand Down Expand Up @@ -789,6 +808,12 @@ func (s *Service) GetPostFileURL(ctx *gin.Context) {
}
}

// Check existence first
if _, err := s.mc.StatObject(ctx.Request.Context(), s.bucket, objectName, minio.StatObjectOptions{}); err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{"message": "file not found"})
return
}

urlStr, err := s.presignedOrCDNURL(ctx.Request.Context(), objectName, time.Duration(expires)*time.Second)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "could not generate url"})
Expand Down Expand Up @@ -817,6 +842,12 @@ func (s *Service) GetProfileURL(ctx *gin.Context) {
}
}

// Check existence first
if _, err := s.mc.StatObject(ctx.Request.Context(), s.bucket, objectName, minio.StatObjectOptions{}); err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{"message": "profile not found"})
return
}

urlStr, err := s.presignedOrCDNURL(ctx.Request.Context(), objectName, time.Duration(expires)*time.Second)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "could not generate url"})
Expand Down
4 changes: 2 additions & 2 deletions microservices/the_monkeys_gateway/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ func main() {
server.router.Use(gin.Recovery())
// retain default gin logger? use custom zap middleware later
// server.router.Use(gin.Logger())
server.router.MaxMultipartMemory = 8 << 20
server.router.MaxMultipartMemory = 100 << 20

// Apply security middleware
server.router.Use(secure.New(secure.Config{
FrameDeny: true,
FrameDeny: false,
ContentTypeNosniff: true,
BrowserXssFilter: true,
ContentSecurityPolicy: "default-src 'self';", // Customize as needed
Expand Down
Loading