Skip to content

Conversation

@0xMoonrise
Copy link

gin challenge-2-middleware Solution

Submitted by: @0xMoonrise
Package: gin
Challenge: challenge-2-middleware

Description

This PR contains my solution for gin challenge-2-middleware.

Changes

  • Added solution file to packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go

Testing

  • Solution passes all test cases
  • Code follows Go best practices

Thank you for reviewing my submission! 🚀

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Warning

Rate limit exceeded

@0xMoonrise has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6c1688c and 9b5daf2.

📒 Files selected for processing (1)
  • packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go

Walkthrough

Implements a Go Gin HTTP server in a single file providing an in-memory Article store, CRUD endpoints, an admin stats endpoint, and a layered set of middleware (request ID, logging, auth, CORS, rate limiting, content-type enforcement, error handling, request counting).

Changes

Cohort / File(s) Summary
Gin HTTP Server Implementation
packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go
Added ~532-line file implementing Article and APIResponse types, an in-memory store with auto-incrementing IDs, CRUD handlers (GET /ping, GET/POST/PUT/DELETE /articles, GET /articles/:id), admin stats (GET /admin/stats), helper functions (find/validate CRUD), and create/update/delete logic.
Middleware & Request Flow (same file)
packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go
Added layered middleware: ErrorHandler, RequestID, Logging, Auth (API key + role), CORS, RateLimit (per-IP), ContentType (application/json), RequestCounter. Middleware sets context values/headers and enforces auth/rate/content rules.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (HTTP)
    participant Gin as Gin Server
    participant MW as Middleware Chain
    participant Store as In-memory Store
    participant Logger as Logger

    Client->>Gin: HTTP request (e.g., POST /articles)
    Gin->>MW: enter middleware chain
    MW->>MW: RequestIDMiddleware (assign UUID header)
    MW->>Logger: LoggingMiddleware (start, record requestID)
    MW->>MW: CORSMiddleware (handle OPTIONS / set headers)
    MW->>MW: RateLimitMiddleware (check & decrement quota)
    MW->>MW: ContentTypeMiddleware (validate application/json)
    MW->>MW: AuthMiddleware (validate API key → role in ctx)
    MW->>Gin: pass to handler if allowed
    Gin->>Store: createArticle / update / fetch / delete
    Store-->>Gin: return result
    Gin->>Logger: LoggingMiddleware (record latency, status, requestID)
    Gin-->>Client: HTTP response (APIResponse with requestID)
Loading

Note: rectangles colored via rgba are not required in this simple sequence flow.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: adding a solution file for the gin challenge-2-middleware challenge by a specific user, which matches the changeset.
Description check ✅ Passed The description is clearly related to the changeset, explaining the submission of a solution for gin challenge-2-middleware with appropriate details about changes and testing.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go`:
- Around line 154-163: The middleware aborts the request but does not stop
execution: after the AbortWithStatusJSON call inside the API key check you must
immediately return to avoid running c.Set("role", r) and c.Next(); modify the
handler where the variables ok and r are checked so that when !ok you call
c.AbortWithStatusJSON(...) and then return from the handler, ensuring
unauthorized requests do not proceed.
- Around line 436-437: The update currently overwrites the article with
updatedArticle from the request body, which loses the original ID and CreatedAt;
before setting updatedArticle.UpdatedAt and writing back to articles[id-1], copy
the original article (e.g., orig := articles[id-1]) and set updatedArticle.ID =
orig.ID and updatedArticle.CreatedAt = orig.CreatedAt, then set
updatedArticle.UpdatedAt = time.Now() and assign articles[id-1] =
updatedArticle.
- Around line 38-54: The global articles slice and nextID need synchronization:
add a package-level sync.RWMutex named articlesMu and use
articlesMu.RLock()/RUnlock() for all read-only access of articles and nextID
(e.g., list/get handlers) and articlesMu.Lock()/Unlock() for all mutations
(e.g., create/update/delete handlers and when incrementing nextID); ensure every
function that reads or writes the articles slice or nextID refers to these locks
(use defer to release) so no concurrent handler accesses mutate the shared state
without locking.
- Around line 342-361: The handler incorrectly assumes IDs map to slice indices
(using articles[id-1]) which breaks after deletions; replace that lookup with
the existing helper findArticleByID (or change storage to map[int]Article) to
perform a proper ID-based lookup and return 404 if not found; specifically, in
the block using articles and APIResponse/c.JSON, call findArticleByID(id,
articles) to get the Article and a found flag (or lookup in map), and then
respond with http.StatusNotFound when not found, otherwise return the found
Article in the http.StatusOK APIResponse.
- Around line 287-298: ErrorHandlerMiddleware currently uses unsafe type
assertions on recovered and request_id which can panic; update the
CustomRecovery handler in ErrorHandlerMiddleware to use safe type checks (the
comma-ok idiom) for recovered (e.g., try recovered.(error) and fall back to
fmt.Errorf("%v", recovered) or recoveredString) and for requestID (check
c.Get("request_id") return values and use a default like empty string or
"unknown" if not present), then build APIResponse using those safe values
instead of direct assertions; reference ErrorHandlerMiddleware, recovered, and
the request_id key and ensure it handles non-error panic values and missing
request IDs gracefully.
♻️ Duplicate comments (1)
packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go (1)

459-467: Same ID-index lookup issue as other handlers.

This suffers from the same problem flagged in getArticle - the ID-to-index mapping breaks with non-contiguous IDs. Switching to a map-based storage would resolve this across all handlers.

🧹 Nitpick comments (4)
packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go (4)

202-210: Unused constructor parameters.

The requests and duration parameters are accepted but never used. The rate limit is hardcoded in getVisitor at line 232. Either use these parameters or remove them.

♻️ Proposed fix to use the parameters
+type RateLimiter struct {
+  visitors map[string]*visitor
+  mu       sync.RWMutex
+  rate     rate.Limit
+  burst    int
+}

 func NewRateLimiter(requests int, duration time.Duration) *RateLimiter {
   rl := &RateLimiter{
     visitors: make(map[string]*visitor),
+    rate:     rate.Every(duration / time.Duration(requests)),
+    burst:    requests,
   }

   go rl.cleanupVisitors()

   return rl
 }

 func (rl *RateLimiter) getVisitor(ip string) *rate.Limiter {
   rl.mu.Lock()
   defer rl.mu.Unlock()

   v, exists := rl.visitors[ip]
   if !exists {
-    limiter := rate.NewLimiter(rate.Every(time.Minute), 100)
+    limiter := rate.NewLimiter(rl.rate, rl.burst)
     rl.visitors[ip] = &visitor{limiter, time.Now()}
     return limiter
   }
   // ...
 }

509-515: Unused function with incorrect boundary check.

  1. This function is never called - consider removing it or using it in the handlers to reduce duplication.
  2. Line 511: id < 0 should be id <= 0 since ID 0 is invalid (IDs start at 1).
♻️ Proposed fix
 func findArticleByID(id int) (*Article, int) {
-  if id > len(articles) || id < 0 {
+  if id > len(articles) || id <= 0 {
     return nil, -1
   }
   return &articles[id-1], id - 1
 }

111-111: Use camelCase for variable names per Go conventions.

request_id should be requestID to follow Go naming conventions.


494-498: Consider tracking actual uptime.

The uptime value is hardcoded to "24h". For accurate stats, track the server start time and compute the actual uptime.

var serverStartTime = time.Now()

// In getStats:
stats := map[string]interface{}{
    "total_articles": len(articles),
    "total_requests": atomic.LoadInt64(&servedRequests),
    "uptime":         time.Since(serverStartTime).String(),
}

Comment on lines +38 to +54
// In-memory storage
var articles = []Article{
{ID: 1,
Title: "Getting Started with Go",
Content: "Go is a programming language...",
Author: "John Doe",
CreatedAt: time.Now(),
UpdatedAt: time.Now()},

{ID: 2,
Title: "Web Development with Gin",
Content: "Gin is a web framework...",
Author: "Jane Smith",
CreatedAt: time.Now(),
UpdatedAt: time.Now()},
}
var nextID = 3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Data race on shared mutable state.

The global articles slice and nextID counter are accessed and modified by multiple concurrent requests without synchronization. This will cause data races under concurrent load.

🔒 Proposed fix using sync.RWMutex
+var articlesMu sync.RWMutex
 var articles = []Article{
   {ID: 1,
     Title:     "Getting Started with Go",
     Content:   "Go is a programming language...",
     Author:    "John Doe",
     CreatedAt: time.Now(),
     UpdatedAt: time.Now()},

   {ID: 2,
     Title:     "Web Development with Gin",
     Content:   "Gin is a web framework...",
     Author:    "Jane Smith",
     CreatedAt: time.Now(),
     UpdatedAt: time.Now()},
 }
 var nextID = 3

Then wrap all reads with articlesMu.RLock()/RUnlock() and all writes with articlesMu.Lock()/Unlock().

🤖 Prompt for AI Agents
In `@packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go`
around lines 38 - 54, The global articles slice and nextID need synchronization:
add a package-level sync.RWMutex named articlesMu and use
articlesMu.RLock()/RUnlock() for all read-only access of articles and nextID
(e.g., list/get handlers) and articlesMu.Lock()/Unlock() for all mutations
(e.g., create/update/delete handlers and when incrementing nextID); ensure every
function that reads or writes the articles slice or nextID refers to these locks
(use defer to release) so no concurrent handler accesses mutate the shared state
without locking.

Comment on lines +342 to +361
if len(articles) < id || id <= 0 {
c.JSON(http.StatusNotFound, APIResponse{
Success: false,
Error: "Id not found",
})
return
}

if (articles[id-1] == Article{}) {
c.JSON(http.StatusNotFound, APIResponse{
Success: false,
Error: "Id not found",
})
return
}

c.JSON(http.StatusOK, APIResponse{
Success: true,
Data: articles[id-1],
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Article lookup by ID assumes contiguous array indices.

The logic articles[id-1] assumes article IDs directly map to array indices (ID 1 at index 0, ID 2 at index 1, etc.). This breaks after deletions since IDs remain unchanged but the array can have gaps or the mapping becomes inconsistent.

Consider using a map map[int]Article for O(1) lookups by ID, or use the existing findArticleByID helper (which is defined but unused).

♻️ Suggested approach using a map
-var articles = []Article{...}
+var articles = map[int]Article{
+  1: {ID: 1, Title: "Getting Started with Go", ...},
+  2: {ID: 2, Title: "Web Development with Gin", ...},
+}

 func getArticle(c *gin.Context) {
   // ...
-  if len(articles) < id || id <= 0 {
-    // ...
-  }
-  if (articles[id-1] == Article{}) {
+  article, exists := articles[id]
+  if !exists {
     c.JSON(http.StatusNotFound, APIResponse{
       Success: false,
       Error:   "Id not found",
     })
     return
   }
-  c.JSON(http.StatusOK, APIResponse{Success: true, Data: articles[id-1]})
+  c.JSON(http.StatusOK, APIResponse{Success: true, Data: article})
 }
🤖 Prompt for AI Agents
In `@packages/gin/challenge-2-middleware/submissions/0xMoonrise/solution.go`
around lines 342 - 361, The handler incorrectly assumes IDs map to slice indices
(using articles[id-1]) which breaks after deletions; replace that lookup with
the existing helper findArticleByID (or change storage to map[int]Article) to
perform a proper ID-based lookup and return 404 if not found; specifically, in
the block using articles and APIResponse/c.JSON, call findArticleByID(id,
articles) to get the Article and a found flag (or lookup in map), and then
respond with http.StatusNotFound when not found, otherwise return the found
Article in the http.StatusOK APIResponse.

0xMoonrise and others added 4 commits January 18, 2026 17:42
…ution.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ution.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ution.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant