Skip to content

Tran/users admin#5

Open
tnt07-t wants to merge 11 commits intomainfrom
tran/users-admin
Open

Tran/users admin#5
tnt07-t wants to merge 11 commits intomainfrom
tran/users-admin

Conversation

@tnt07-t
Copy link
Contributor

@tnt07-t tnt07-t commented Mar 11, 2026

Overview

Added admin authorization and an isAdmin field to the Users model. Also removed legacy GitHub-related routes and models, including services listing functionality.

Changes Made

  • added isAdmin to the Users model
  • added requireAdmin middleware
  • restricted GET /users to admin users only
  • removed serviceslisting and other legacy Hustle routes/models

Test Coverage

Tested locally.

  • normal user requesting GET /users returns the expected error
  • admin user requesting GET /users successfully receives all users

Next Steps

  • Make admin-creation route (admin-protected)

Summary by CodeRabbit

  • New Features

    • Admin role and admin-only endpoint to list users
  • Documentation

    • Comprehensive API reference added (auth flows, token refresh, FCM device/register/test endpoints, protected/admin routes, error responses)
  • Removed Features

    • Service listings and all related service management APIs removed
  • Chores

    • Database compose configuration switched to environment variables; healthcheck and restart behavior improved
  • Bug Fixes

    • Clarified user-creation error messaging

Copy link
Contributor

@JoshD94 JoshD94 left a comment

Choose a reason for hiding this comment

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

lgtm

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

Move user-listing behind an admin-only route, add RequireAdmin middleware that checks DB for is_admin, extend User model with IsAdmin and time-based timestamps, include FCMToken in migrations, add API.md documentation, parameterize Postgres creds in docker-compose, and remove the entire services/listings feature (controllers, models, service layer).

Changes

Cohort / File(s) Summary
API Documentation
API.md
Adds comprehensive API reference: base URL, public /healthcheck, auth endpoints (POST /api/verify-token, POST /api/refresh-token), protected user/FCM endpoints, admin-only /api/admin/users, and standardized error responses.
Routing & Server
main.go
Removes previous /users routes from protected group; adds admin route group /api/admin with RequireAuth + RequireAdmin and registers GET /admin/userscontrollers.FindUsers.
Auth Middleware
middleware/auth.go
Adds RequireAdmin(c *gin.Context) which extracts uid from context, looks up user in DB, aborts 401/403 as appropriate, and proceeds only if IsAdmin is true.
User Controllers
controllers/users.go
Removes public CreateUser handler; updates users listing handler to return []models.UserResponse; minor formatting and response construction tweaks.
Models & Migrations
models/users.go, models/setup.go
User struct updated: added IsAdmin bool, CreatedAt/UpdatedAt time.Time; removed CreateUserInput and Seller, added UserResponse; AutoMigrate now includes FCMToken.
Removed: Services Feature
controllers/services.go, models/services.go, services/listingservice.go
Entire services/listings feature deleted: controllers, models, and service-layer (all public types, constructors, methods, and CRUD functions removed).
Infrastructure
docker-compose.yml
Replaces hard-coded Postgres credentials with POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD env vars; updates pg_isready healthcheck to use those vars; adds restart: unless-stopped.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Server as API Server
    participant DB as Database

    Client->>Server: GET /api/admin/users (Authorization: Bearer <access_token>)
    Server->>Server: RequireAuth validates token, extracts uid
    Server->>Server: RequireAdmin reads uid from context
    Server->>DB: SELECT * FROM users WHERE firebase_uid = uid
    DB-->>Server: user row (IsAdmin = true/false)
    alt IsAdmin true
        Server->>Server: controllers.FindUsers queries users
        Server->>DB: SELECT * FROM users
        DB-->>Server: users list
        Server-->>Client: 200 OK + users list
    else IsAdmin false
        Server-->>Client: 403 Forbidden
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I hopped through routes and database springs,

Tokens swapped paws and tiny wings,
Admin doors now firmly barred,
Services vanished — quiet yard,
Docker tends the garden rings.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Tran/users admin' is vague and lacks specific information about the changes. It references a branch name rather than clearly describing what was implemented or changed. Use a more descriptive title such as 'Add admin authorization to users API' or 'Restrict user listing to admin-only access' to clearly convey the main objective.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description covers the main objectives and includes test coverage, but lacks detailed explanation of how changes work and the rationale for removing legacy components.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tran/users-admin
📝 Coding Plan
  • Generate coding plan for human review comments

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

Tip

CodeRabbit can enforce grammar and style rules using `languagetool`.

Configure the reviews.tools.languagetool setting to enable/disable rules and categories. Refer to the LanguageTool Community to learn more.

Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
main.go (3)

31-32: ⚠️ Potential issue | 🟡 Minor

Duplicate AutoMigrate call.

FCMToken is already auto-migrated in models.ConnectDatabase() (line 41 of models/setup.go). This duplicate call is unnecessary.

🗑️ Proposed fix: Remove duplicate migration
-	// Migrate fcm token table
-	models.DB.AutoMigrate(&models.FCMToken{})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` around lines 31 - 32, Remove the redundant migration by deleting the
duplicate call to models.DB.AutoMigrate(&models.FCMToken{}) in main.go; the
FCMToken model is already migrated inside models.ConnectDatabase(), so keep
ConnectDatabase()'s migration and remove the
models.DB.AutoMigrate(&models.FCMToken{}) invocation to avoid duplicate work.

37-43: ⚠️ Potential issue | 🟠 Major

Server continues after fatal Firebase initialization failure.

Similar to the database error, the server logs [FATAL] but continues. If Firebase auth fails, all authenticated endpoints will be broken.

🐛 Proposed fix: Exit on fatal errors
 	if _, err := os.Stat(serviceAccountPath); err != nil {
-		log.Printf("[FATAL] Missing service account file: %s (cwd: %s): %v", serviceAccountPath, getwdSafe(), err)
+		log.Fatalf("[FATAL] Missing service account file: %s (cwd: %s): %v", serviceAccountPath, getwdSafe(), err)
 	}
 	ac, err := auth.NewAuthClient(context.Background(), serviceAccountPath)
 	if err != nil {
-		log.Printf("[FATAL] Firebase init failed: %v", err)
+		log.Fatalf("[FATAL] Firebase init failed: %v", err)
 	}

 	// Initialize Firebase Messaging
 	if err := auth.InitFirebase(serviceAccountPath); err != nil {
-		log.Printf("[FATAL] Firebase Messaging init failed: %v", err)
+		log.Fatalf("[FATAL] Firebase Messaging init failed: %v", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` around lines 37 - 43, The code logs fatal messages but continues
running when Firebase auth fails; update the checks around serviceAccountPath
and auth.NewAuthClient to terminate the process on error: after the os.Stat
failure (serviceAccountPath/getwdSafe()) and after auth.NewAuthClient returning
an error, call a process exit (e.g., os.Exit(1)) or use log.Fatalf to log and
exit so the server does not start with a nil/invalid auth client (referencing
serviceAccountPath, getwdSafe(), and auth.NewAuthClient).

27-29: ⚠️ Potential issue | 🟠 Major

Server continues after fatal database connection failure.

Logging [FATAL] suggests the error is unrecoverable, but the server continues to start. This will cause panics or undefined behavior when handlers try to use the nil/invalid DB.

🐛 Proposed fix: Exit on fatal errors
 	if err := models.ConnectDatabase(); err != nil {
-		log.Printf("[FATAL] Database connection failed: %v", err)
+		log.Fatalf("[FATAL] Database connection failed: %v", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` around lines 27 - 29, The code logs a fatal DB error but continues
running; change the main startup path that calls models.ConnectDatabase() so the
process exits on error — e.g., replace the current log.Printf in the if err :=
models.ConnectDatabase(); err != nil block with a terminating call
(log.Fatalf("[FATAL] Database connection failed: %v", err) or log.Printf(...)
followed by os.Exit(1)) so the server does not start with an invalid DB; update
the error handling in main around models.ConnectDatabase() accordingly.
controllers/users.go (1)

17-20: ⚠️ Potential issue | 🟡 Minor

Missing error handling on database query.

DB.Find(&users) can fail, but errors are not checked. Consider handling the error to avoid returning an empty response when the database is unavailable.

🛡️ Proposed fix
 func FindUsers(c *gin.Context) {
 	var users []models.User
-	models.DB.Find(&users)
-
-	c.JSON(http.StatusOK, gin.H{"data": users})
+	if err := models.DB.Find(&users).Error; err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
+		return
+	}
+	c.JSON(http.StatusOK, gin.H{"data": users})
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controllers/users.go` around lines 17 - 20, The DB.Find call can fail but its
error is ignored; update the users listing handler to capture the GORM result
(e.g., res := models.DB.Find(&users)) and check res.Error; on error, log or
return an HTTP 500/appropriate status using the gin.Context (c.JSON) with an
error message instead of continuing to return an empty users array; otherwise
proceed to return the users as before. Ensure you reference the existing users
slice and models.DB.Find call and do not change the response shape on success.
🧹 Nitpick comments (4)
controllers/healthcheck.go (1)

12-22: Good addition of database health verification.

The enhanced health check properly returns 503 when the database is unreachable. Consider logging the error for debugging purposes before returning the unhealthy response.

💡 Optional: Add error logging for debugging
 func HealthCheck(c *gin.Context) {
 	sqlDB, err := models.DB.DB()
 	if err != nil {
+		log.Printf("[WARN] Health check failed to get DB handle: %v", err)
 		c.JSON(http.StatusServiceUnavailable, gin.H{"status": "unhealthy", "database": "disconnected"})
 		return
 	}
 	if err := sqlDB.Ping(); err != nil {
+		log.Printf("[WARN] Health check DB ping failed: %v", err)
 		c.JSON(http.StatusServiceUnavailable, gin.H{"status": "unhealthy", "database": "disconnected"})
 		return
 	}
 	c.JSON(http.StatusOK, gin.H{"status": "healthy", "database": "connected"})
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controllers/healthcheck.go` around lines 12 - 22, In HealthCheck, log the
actual error before returning the 503 so failures are debuggable: when
models.DB.DB() returns err and when sqlDB.Ping() returns err, emit a log entry
(e.g., using the application's logger or log.Printf / c.Error) containing the
error and a short context message, then continue to return the existing c.JSON
unhealthy response; update the error handling around models.DB.DB() and
sqlDB.Ping() accordingly.
models/setup.go (1)

40-44: Consider migration strategy for production.

AutoMigrate on every startup is convenient for development but may cause issues in production (e.g., long-running migrations blocking startup, lack of rollback capability). Consider using a dedicated migration tool (like golang-migrate) for production deployments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/setup.go` around lines 40 - 44, The code currently calls
database.AutoMigrate(&User{}, &FCMToken{}) on startup; for production replace
this approach with a dedicated migration workflow: remove or guard the
database.AutoMigrate call in the setup code and instead generate explicit
up/down SQL migrations (e.g., using golang-migrate) for the User and FCMToken
schema changes, add a startup option or CI/CD step that runs the migration tool
(not AutoMigrate) before app processes traffic, and keep a short runtime
fallback only for local/dev (guarded by an env flag like ENABLE_AUTO_MIGRATE) so
production runs use the migration tool with rollback capability and controlled
execution.
models/users.go (1)

33-34: Consider adjusting log level for expected flow.

"User not found by Firebase UID" is an expected condition during first-time user creation, not an error. Consider using log.Printf("[DEBUG]...") or log.Printf("[INFO]...") to avoid noisy error logs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/users.go` around lines 33 - 34, The lookup that logs "User not found
by Firebase UID" treats an expected first-time-miss as an error; change the log
level in the branch that checks result.Error (the code referencing result.Error
and firebaseUID in models/users.go) from "[ERROR]" to a non-error level such as
"[DEBUG]" or "[INFO]" (e.g., log.Printf("[DEBUG] User not found by Firebase UID
(%s): %v", firebaseUID, result.Error)) so misses during initial user creation
don't produce noisy error logs; keep other error-handling behavior unchanged.
API.md (1)

134-159: Document how the is_admin field is initialized and managed.

The admin routes documentation shows that users must have is_admin = true to access these endpoints, but there's no documentation on:

  1. How the is_admin field is set when users are created (always false by default?)
  2. How to promote a user to admin status
  3. Whether there's an admin creation/promotion endpoint

The PR objectives mention "Add an admin-creation route protected by admin authorization" as a next step, but it would be helpful to document the current state and planned approach for managing admin users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@API.md` around lines 134 - 159, Document that the is_admin boolean field
defaults to false on user creation (mention the is_admin field), state how admin
status can be granted (e.g., via an admin-only promotion endpoint or a one-time
seeded admin user), and explicitly describe the current options: (1) no public
admin creation endpoint today so promotion must be done by DB seed/CLI or an
existing admin calling the admin promotion API, (2) planned admin-creation route
protected by admin authorization (refer to the "admin-creation route" and GET
/api/admin/users), and include guidance on required Authorization: Bearer
<access_token> and that only users with is_admin = true can call
promotion/admin-creation endpoints; additionally note migration/DB default
behavior and any recommended audit/logging for admin promotions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@API.md`:
- Around line 8-14: Update the API docs to match the implemented route: change
the documented endpoint from "GET /healthcheck" to "GET /health" in API.md so it
aligns with the registered handler (the actual route registered as GET /health
in main). Ensure the heading and any example request paths reflect "/health" and
keep the existing response body unchanged.
- Around line 75-101: The POST /api/users endpoint conflicts with the
verify-token flow and will cause DB constraint violations because it calls
DB.Create() without checking for existing users; either remove POST /api/users
entirely (relying on POST /api/verify-token and FindOrCreateUser) or convert it
to an admin-only endpoint by adding an admin privilege check and replacing the
DB.Create() call with the shared FindOrCreateUser logic so it safely handles
existing users; update any route handler named for POST /api/users and ensure
auth/authorization checks (admin flag) are enforced if you keep the endpoint.

In `@controllers/users.go`:
- Around line 46-48: The DB.Create(&user) call in controllers/users.go ignores
errors and can return invalid data to clients; update the CreateUser flow to
check the result := models.DB.Create(&user) (or use tx :=
models.DB.Create(&user)) and if result.Error != nil return an appropriate HTTP
error via c.JSON (e.g., http.StatusBadRequest or http.StatusInternalServerError)
with the error message/context, and avoid returning the user on failure; also
handle result.RowsAffected if needed for additional validation.

---

Outside diff comments:
In `@controllers/users.go`:
- Around line 17-20: The DB.Find call can fail but its error is ignored; update
the users listing handler to capture the GORM result (e.g., res :=
models.DB.Find(&users)) and check res.Error; on error, log or return an HTTP
500/appropriate status using the gin.Context (c.JSON) with an error message
instead of continuing to return an empty users array; otherwise proceed to
return the users as before. Ensure you reference the existing users slice and
models.DB.Find call and do not change the response shape on success.

In `@main.go`:
- Around line 31-32: Remove the redundant migration by deleting the duplicate
call to models.DB.AutoMigrate(&models.FCMToken{}) in main.go; the FCMToken model
is already migrated inside models.ConnectDatabase(), so keep ConnectDatabase()'s
migration and remove the models.DB.AutoMigrate(&models.FCMToken{}) invocation to
avoid duplicate work.
- Around line 37-43: The code logs fatal messages but continues running when
Firebase auth fails; update the checks around serviceAccountPath and
auth.NewAuthClient to terminate the process on error: after the os.Stat failure
(serviceAccountPath/getwdSafe()) and after auth.NewAuthClient returning an
error, call a process exit (e.g., os.Exit(1)) or use log.Fatalf to log and exit
so the server does not start with a nil/invalid auth client (referencing
serviceAccountPath, getwdSafe(), and auth.NewAuthClient).
- Around line 27-29: The code logs a fatal DB error but continues running;
change the main startup path that calls models.ConnectDatabase() so the process
exits on error — e.g., replace the current log.Printf in the if err :=
models.ConnectDatabase(); err != nil block with a terminating call
(log.Fatalf("[FATAL] Database connection failed: %v", err) or log.Printf(...)
followed by os.Exit(1)) so the server does not start with an invalid DB; update
the error handling in main around models.ConnectDatabase() accordingly.

---

Nitpick comments:
In `@API.md`:
- Around line 134-159: Document that the is_admin boolean field defaults to
false on user creation (mention the is_admin field), state how admin status can
be granted (e.g., via an admin-only promotion endpoint or a one-time seeded
admin user), and explicitly describe the current options: (1) no public admin
creation endpoint today so promotion must be done by DB seed/CLI or an existing
admin calling the admin promotion API, (2) planned admin-creation route
protected by admin authorization (refer to the "admin-creation route" and GET
/api/admin/users), and include guidance on required Authorization: Bearer
<access_token> and that only users with is_admin = true can call
promotion/admin-creation endpoints; additionally note migration/DB default
behavior and any recommended audit/logging for admin promotions.

In `@controllers/healthcheck.go`:
- Around line 12-22: In HealthCheck, log the actual error before returning the
503 so failures are debuggable: when models.DB.DB() returns err and when
sqlDB.Ping() returns err, emit a log entry (e.g., using the application's logger
or log.Printf / c.Error) containing the error and a short context message, then
continue to return the existing c.JSON unhealthy response; update the error
handling around models.DB.DB() and sqlDB.Ping() accordingly.

In `@models/setup.go`:
- Around line 40-44: The code currently calls database.AutoMigrate(&User{},
&FCMToken{}) on startup; for production replace this approach with a dedicated
migration workflow: remove or guard the database.AutoMigrate call in the setup
code and instead generate explicit up/down SQL migrations (e.g., using
golang-migrate) for the User and FCMToken schema changes, add a startup option
or CI/CD step that runs the migration tool (not AutoMigrate) before app
processes traffic, and keep a short runtime fallback only for local/dev (guarded
by an env flag like ENABLE_AUTO_MIGRATE) so production runs use the migration
tool with rollback capability and controlled execution.

In `@models/users.go`:
- Around line 33-34: The lookup that logs "User not found by Firebase UID"
treats an expected first-time-miss as an error; change the log level in the
branch that checks result.Error (the code referencing result.Error and
firebaseUID in models/users.go) from "[ERROR]" to a non-error level such as
"[DEBUG]" or "[INFO]" (e.g., log.Printf("[DEBUG] User not found by Firebase UID
(%s): %v", firebaseUID, result.Error)) so misses during initial user creation
don't produce noisy error logs; keep other error-handling behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3ac18f5-d016-47c2-8ff8-ddbdb50d1f17

📥 Commits

Reviewing files that changed from the base of the PR and between e2903fc and 33712a2.

📒 Files selected for processing (11)
  • API.md
  • controllers/healthcheck.go
  • controllers/users.go
  • docker-compose.yml
  • hustle-backend
  • main.go
  • middleware/auth.go
  • models/errors.go
  • models/setup.go
  • models/users.go
  • test_auth_flow.md
💤 Files with no reviewable changes (1)
  • test_auth_flow.md

API.md Outdated
Comment on lines +8 to +14
### GET /healthcheck
Check if the server is running.

**Response**
```json
{ "message": "Chimes is healthy!" }
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find the actual health check endpoint registration in the codebase.

# Search for health check route definitions
rg -nP --type=go '\bGET\b.*health' -A2 -B2

Repository: cuappdev/chimes-backend

Length of output: 468


Update health check endpoint path in documentation from /healthcheck to /health.

The actual implementation registers the endpoint as GET /health (main.go line 52), but the API documentation incorrectly shows /healthcheck. Update line 8 of API.md to match the actual endpoint.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@API.md` around lines 8 - 14, Update the API docs to match the implemented
route: change the documented endpoint from "GET /healthcheck" to "GET /health"
in API.md so it aligns with the registered handler (the actual route registered
as GET /health in main). Ensure the heading and any example request paths
reflect "/health" and keep the existing response body unchanged.

Copy link

@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: 1

🧹 Nitpick comments (1)
main.go (1)

79-79: Add pagination before exposing full user lists.

Line 79 points to controllers.FindUsers, and controllers/users.go Line 16 currently does an unbounded models.DB.Find(&users). This can degrade latency/memory as the table grows. Please add limit/offset (or cursor pagination) on this path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.go` at line 79, The FindUsers endpoint currently returns an unbounded
user list by calling models.DB.Find(&users) in controllers.FindUsers, which can
cause latency/memory issues; modify controllers.FindUsers to accept pagination
parameters (e.g., query params "limit" and "offset" or a cursor),
validate/default them, and replace the unbounded call with a paginated DB query
(e.g., models.DB.Limit(limit).Offset(offset).Find(&users) or equivalent cursor
logic), and return pagination metadata (total/count/next cursor) alongside the
users; ensure you update any request parsing in controllers.FindUsers and use
safe defaults and upper bounds for limit to prevent abuse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@models/users.go`:
- Line 11: The User model currently exposes the Refresh_Token field via JSON;
change the struct field tag Refresh_Token in models.User to `json:"-"` (or
remove any json tag) so it is never serialized, and update controllers/users.go
(where full models.User objects are returned) to return a safe DTO or explicit
response object that omits sensitive fields (or copy only allowed fields from
models.User) to ensure refresh tokens are not sent in API responses.

---

Nitpick comments:
In `@main.go`:
- Line 79: The FindUsers endpoint currently returns an unbounded user list by
calling models.DB.Find(&users) in controllers.FindUsers, which can cause
latency/memory issues; modify controllers.FindUsers to accept pagination
parameters (e.g., query params "limit" and "offset" or a cursor),
validate/default them, and replace the unbounded call with a paginated DB query
(e.g., models.DB.Limit(limit).Offset(offset).Find(&users) or equivalent cursor
logic), and return pagination metadata (total/count/next cursor) alongside the
users; ensure you update any request parsing in controllers.FindUsers and use
safe defaults and upper bounds for limit to prevent abuse.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9a6a6552-c466-4e2b-8622-9e634bae40e7

📥 Commits

Reviewing files that changed from the base of the PR and between 33712a2 and 2228614.

📒 Files selected for processing (2)
  • main.go
  • models/users.go

Copy link

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
controllers/users.go (1)

16-21: ⚠️ Potential issue | 🟠 Major

Missing error handling on database query.

models.DB.Find(&users) can fail (e.g., connection issues), but errors are silently ignored. The endpoint will return an empty array with 200 OK even on failure.

🐛 Proposed fix to add error handling
 func FindUsers(c *gin.Context) {
 	var users []models.User
-	models.DB.Find(&users)
-
-	c.JSON(http.StatusOK, gin.H{"data": users})
+	if err := models.DB.Find(&users).Error; err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch users"})
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{"data": users})
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controllers/users.go` around lines 16 - 21, In FindUsers, check the result of
models.DB.Find(&users) for an error (use the returned result's Error field)
instead of ignoring it; if there is an error, log or return a non-200 response
(e.g., c.JSON with http.StatusInternalServerError and an error message) and only
return the users with http.StatusOK when no error occurred; update references to
models.DB.Find, the users slice, and the c.JSON response accordingly to ensure
failures (DB connection/query errors) are surfaced instead of returning an empty
200 response.
🧹 Nitpick comments (1)
controllers/users.go (1)

47-53: Consider using appropriate HTTP status codes.

Two suggestions:

  1. Return http.StatusCreated (201) for successful resource creation instead of http.StatusOK (200).
  2. The comment mentions rejecting duplicates, but returning 500 for duplicate key violations is misleading. Consider returning 409 Conflict for duplicate firebase_uid.
♻️ Proposed improvement
 	// Reject duplicate users — firebase_uid has a unique constraint in the DB
 	if err := models.DB.Create(&user).Error; err != nil {
+		if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "UNIQUE constraint") {
+			c.JSON(http.StatusConflict, gin.H{"error": "user already exists"})
+			return
+		}
 		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"})
 		return
 	}

-	c.JSON(http.StatusOK, gin.H{"data": user})
+	c.JSON(http.StatusCreated, gin.H{"data": user})
 }

Note: For more robust duplicate detection, consider using GORM's error types or database-specific error codes rather than string matching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controllers/users.go` around lines 47 - 53, The handler currently returns 200
on create and 500 for any DB error; change the success response to use
http.StatusCreated (201) when models.DB.Create(&user) succeeds, and detect
duplicate-key/unique-constraint errors from the Create call (inspect the
returned err from models.DB.Create(&user).Error using DB-specific error codes or
a string/check helper) to return http.StatusConflict (409) for duplicate
firebase_uid, otherwise return the existing 500 for other errors; update the
c.JSON calls accordingly (references: models.DB.Create(&user).Error and the
subsequent c.JSON(...) lines).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@controllers/users.go`:
- Around line 16-21: In FindUsers, check the result of models.DB.Find(&users)
for an error (use the returned result's Error field) instead of ignoring it; if
there is an error, log or return a non-200 response (e.g., c.JSON with
http.StatusInternalServerError and an error message) and only return the users
with http.StatusOK when no error occurred; update references to models.DB.Find,
the users slice, and the c.JSON response accordingly to ensure failures (DB
connection/query errors) are surfaced instead of returning an empty 200
response.

---

Nitpick comments:
In `@controllers/users.go`:
- Around line 47-53: The handler currently returns 200 on create and 500 for any
DB error; change the success response to use http.StatusCreated (201) when
models.DB.Create(&user) succeeds, and detect duplicate-key/unique-constraint
errors from the Create call (inspect the returned err from
models.DB.Create(&user).Error using DB-specific error codes or a string/check
helper) to return http.StatusConflict (409) for duplicate firebase_uid,
otherwise return the existing 500 for other errors; update the c.JSON calls
accordingly (references: models.DB.Create(&user).Error and the subsequent
c.JSON(...) lines).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b89c3b44-e9da-4f9e-820e-cf84ac74bd11

📥 Commits

Reviewing files that changed from the base of the PR and between 2228614 and 7e88a78.

📒 Files selected for processing (4)
  • controllers/services.go
  • controllers/users.go
  • models/services.go
  • services/listingservice.go
💤 Files with no reviewable changes (3)
  • services/listingservice.go
  • models/services.go
  • controllers/services.go

Copy link

@claiireyu claiireyu left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
models/users.go (1)

34-47: ⚠️ Potential issue | 🟠 Major

FindOrCreateUser treats all query errors as "not found".

On Lines 34-47, any DB error enters the create path. Only ErrRecordNotFound should do that; other errors should return immediately. This causes connection failures, query errors, and other DB issues to incorrectly trigger user creation instead of returning an error.

🛠️ Proposed fix
 import (
+	"errors"
 	"log"
 	"time"
+
+	"gorm.io/gorm"
 )
@@
-	if result.Error != nil {
-		log.Printf("[ERROR] User not found by Firebase UID (%s): %v", firebaseUID, result.Error)
-		// User doesn't exist, create new one
-		user = User{
-			Firebase_UID: firebaseUID,
-			Email:        email,
-			FirstName:    firstName,
-			LastName:     lastName,
-		}
-
-		if err := DB.Create(&user).Error; err != nil {
-			log.Printf("[ERROR] Failed to create user (Firebase UID: %s): %v", firebaseUID, err)
-			return nil, err
-		}
-	}
+	if result.Error != nil {
+		if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
+			log.Printf("[ERROR] Failed to query user (Firebase UID: %s): %v", firebaseUID, result.Error)
+			return nil, result.Error
+		}
+
+		user = User{
+			Firebase_UID: firebaseUID,
+			Email:        email,
+			FirstName:    firstName,
+			LastName:     lastName,
+		}
+
+		if err := DB.Create(&user).Error; err != nil {
+			log.Printf("[ERROR] Failed to create user (Firebase UID: %s): %v", firebaseUID, err)
+			return nil, err
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/users.go` around lines 34 - 47, FindOrCreateUser currently treats any
DB error as "not found" and proceeds to create a user; change the logic to only
enter the create path when the query error is gorm.ErrRecordNotFound (use
errors.Is(result.Error, gorm.ErrRecordNotFound)). If result.Error is non-nil and
not ErrRecordNotFound, immediately return nil and the error instead of creating
a new User; keep the existing DB.Create(&user) and error logging for the actual
create path. Make sure to reference result.Error, gorm.ErrRecordNotFound,
FindOrCreateUser, and DB.Create when applying the fix.
♻️ Duplicate comments (1)
models/users.go (1)

11-11: ⚠️ Potential issue | 🟠 Major

Refresh tokens are still JSON-exposed via User.

Line 11 keeps json:"refresh_token", so serializing models.User can leak credential material.

🔐 Proposed fix
-	Refresh_Token string    `json:"refresh_token"`
+	Refresh_Token string    `json:"-"` // never serialize refresh tokens
#!/bin/bash
# Verify whether refresh token is still JSON-tagged and where User might be serialized.
rg -n -C2 --type=go 'Refresh_Token\s+string\s+`json:"refresh_token"`|type User struct|c\.JSON\('

Expected result: field remains tagged for JSON output and should be hardened to json:"-".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/users.go` at line 11, The User struct's Refresh_Token field is still
annotated as json:"refresh_token" causing models.User serialization to leak
credentials; update the Refresh_Token field's struct tag to json:"-" to prevent
JSON marshaling, and scan usages of models.User (look for the User struct,
Refresh_Token, and call sites like c.JSON or any json.Marshal) to remove or
replace any code that relies on that field being present in JSON (or explicitly
map safe fields for responses).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controllers/users.go`:
- Around line 16-17: In FindUsers, the GORM query currently calls
models.DB.Find(&users) with users typed as []models.UserResponse which causes
GORM to target the wrong table (user_responses) and hides failures; change the
query to use models.DB.Model(&models.User{}).Find(&users) to explicitly target
the users table, add proper error handling on the result (check result.Error and
handle non-nil by logging and returning an appropriate non-200 HTTP response),
and ensure the handler returns the expected payload only on success; keep types
as []models.UserResponse for response mapping but query from
Model(&models.User{}).

---

Outside diff comments:
In `@models/users.go`:
- Around line 34-47: FindOrCreateUser currently treats any DB error as "not
found" and proceeds to create a user; change the logic to only enter the create
path when the query error is gorm.ErrRecordNotFound (use errors.Is(result.Error,
gorm.ErrRecordNotFound)). If result.Error is non-nil and not ErrRecordNotFound,
immediately return nil and the error instead of creating a new User; keep the
existing DB.Create(&user) and error logging for the actual create path. Make
sure to reference result.Error, gorm.ErrRecordNotFound, FindOrCreateUser, and
DB.Create when applying the fix.

---

Duplicate comments:
In `@models/users.go`:
- Line 11: The User struct's Refresh_Token field is still annotated as
json:"refresh_token" causing models.User serialization to leak credentials;
update the Refresh_Token field's struct tag to json:"-" to prevent JSON
marshaling, and scan usages of models.User (look for the User struct,
Refresh_Token, and call sites like c.JSON or any json.Marshal) to remove or
replace any code that relies on that field being present in JSON (or explicitly
map safe fields for responses).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6a5cbe4-42ea-4754-9b84-88e5a598822b

📥 Commits

Reviewing files that changed from the base of the PR and between 7e88a78 and d7ec741.

📒 Files selected for processing (4)
  • API.md
  • controllers/users.go
  • main.go
  • models/users.go
✅ Files skipped from review due to trivial changes (1)
  • API.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • main.go

Comment on lines +16 to 17
var users []models.UserResponse
models.DB.Find(&users)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify DTO-based query + absence of explicit model/table binding.
rg -n -C2 --type=go '\bfunc FindUsers\b|DB\.Find\(&users\)|Model\(&models\.User\{\}\)|Select\('
rg -n -C2 --type=go 'type UserResponse struct|TableName\('

Repository: cuappdev/chimes-backend

Length of output: 612


🏁 Script executed:

cat -n models/users.go

Repository: cuappdev/chimes-backend

Length of output: 2074


🏁 Script executed:

rg -n "type User struct|TableName\(" models/users.go -A 5

Repository: cuappdev/chimes-backend

Length of output: 358


🏁 Script executed:

rg -n "\.Model\(|\.Table\(" controllers/users.go

Repository: cuappdev/chimes-backend

Length of output: 49


🏁 Script executed:

rg -n "DB\s*=" --type=go | head -20

Repository: cuappdev/chimes-backend

Length of output: 100


🏁 Script executed:

fd -e go -x rg -l "gorm\|database" {} + | head -10

Repository: cuappdev/chimes-backend

Length of output: 570


🏁 Script executed:

cat -n main.go 2>/dev/null || cat -n cmd/main.go 2>/dev/null || find . -name "main.go" -type f -exec cat -n {} \;

Repository: cuappdev/chimes-backend

Length of output: 2981


🏁 Script executed:

cat -n models/setup.go

Repository: cuappdev/chimes-backend

Length of output: 1743


🏁 Script executed:

rg -n "naming\|TableName\|NamingStrategy" --type=go

Repository: cuappdev/chimes-backend

Length of output: 49


FindUsers queries with the wrong table due to DTO type mismatch—fix by using .Model(&models.User{}) and add error handling.

On Line 17, models.DB.Find(&users) uses []models.UserResponse as the destination. GORM's default naming convention (SnakeCase) infers the table name from the struct type, so this targets user_responses instead of users. Additionally, UserResponse is never migrated (only User and FCMToken are in AutoMigrate), so the query will fail silently. The missing error handler also means failures return HTTP 200 with empty data.

Use .Model(&models.User{}) to explicitly target the correct table and add error handling:

Proposed fix
 func FindUsers(c *gin.Context) {
 	var users []models.UserResponse
-	models.DB.Find(&users)
+	result := models.DB.Model(&models.User{}).
+		Select("id, first_name, last_name, email").
+		Find(&users)
+	if result.Error != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
+		return
+	}
 
 	c.JSON(http.StatusOK, gin.H{"data": users})
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var users []models.UserResponse
models.DB.Find(&users)
func FindUsers(c *gin.Context) {
var users []models.UserResponse
result := models.DB.Model(&models.User{}).
Select("id, first_name, last_name, email").
Find(&users)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
return
}
c.JSON(http.StatusOK, gin.H{"data": users})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controllers/users.go` around lines 16 - 17, In FindUsers, the GORM query
currently calls models.DB.Find(&users) with users typed as []models.UserResponse
which causes GORM to target the wrong table (user_responses) and hides failures;
change the query to use models.DB.Model(&models.User{}).Find(&users) to
explicitly target the users table, add proper error handling on the result
(check result.Error and handle non-nil by logging and returning an appropriate
non-200 HTTP response), and ensure the handler returns the expected payload only
on success; keep types as []models.UserResponse for response mapping but query
from Model(&models.User{}).

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.

3 participants