Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/internal apis for users #1885

Merged
merged 8 commits into from
Feb 21, 2025
Merged

feat/internal apis for users #1885

merged 8 commits into from
Feb 21, 2025

Conversation

motatoes
Copy link
Contributor

@motatoes motatoes commented Feb 20, 2025

  • add internal endpoints for creation of user and org
  • support internal api for users

Summary by CodeRabbit

  • New Features
    • Introduced secure endpoints enabling the creation of users and organizations.
    • Enhanced internal authentication processes to ensure consistent security.
    • Expanded user profile capabilities by incorporating additional data fields such as email, external identifiers, and organization associations.
  • Bug Fixes
    • Improved error handling for user and organization creation processes.
  • Database Changes
    • Updated user table to include new fields for email, external ID, and organization ID.
    • Added unique indexes to ensure data integrity for email and external source combinations.
    • Renamed organization ID column for clarity and established foreign key constraints.

Copy link
Contributor

coderabbitai bot commented Feb 20, 2025

Walkthrough

The pull request introduces conditional logic for internal API endpoints in backend/bootstrap/main.go, replacing the previous authorization method with a new one based on the DIGGER_ENABLE_INTERNAL_ENDPOINTS environment variable. Two new POST endpoints, _internal/api/create_user and _internal/api/upsert_org, are added, both utilizing the updated authorization middleware. Corresponding methods in the DiggerController handle user and organization creation with structured error handling. Additionally, the SQL migration scripts enhance the "users" table by adding new fields and indexes for email and external identifiers.

Changes

File(s) Change Summary
backend/bootstrap/main.go
backend/middleware/webhook.go
Updated internal authorization middleware: renamed WebhookAuth() to InternalApiAuth(). Modified _internal/update_repo_cache to use InternalApiAuth(), and added new POST endpoints (_internal/api/create_user and _internal/api/upsert_org) secured with the same middleware.
backend/controllers/internal_users.go Added new methods CreateUserInternal and UpsertOrgInternal in the DiggerController to handle JSON request binding, error logging, and interaction with the database for user and organization creation.
backend/models/storage.go
backend/models/user.go
Introduced a new CreateUser method to create user records. Updated the User struct by adding fields for Email, ExternalId, and OrganisationId (with OrganisationId as an optional field), while adjusting the position of Username.
backend/migrations/20250220084846.sql
backend/migrations/20250220172054.sql
backend/migrations/20250220172321.sql
backend/migrations/20250220173053.sql
backend/migrations/20250220173439.sql
Modified the "users" table to add new columns: email, external_id, and external_source, and created unique indexes on email and the combination of external_source and external_id. Renamed org_id to organisation_id and added a foreign key constraint for referential integrity.
backend/models/orgs.go Updated the GORM tag for the Name field in the Organisation struct to create a non-unique index instead of a unique index.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant M as InternalApiAuth Middleware
    participant D as DiggerController
    participant DB as Database

    C->>M: POST /_internal/api/create_user
    M-->>C: Proceed if authenticated
    M->>D: Forward request
    D->>DB: GetOrganisation(externalOrgId)
    DB-->>D: Organisation data or error
    D->>DB: CreateUser(email, externalId, orgId, username)
    DB-->>D: New User record or error
    D->>C: Respond with JSON { user_id }
sequenceDiagram
    participant C as Client
    participant M as InternalApiAuth Middleware
    participant D as DiggerController
    participant DB as Database

    C->>M: POST /_internal/api/upsert_org
    M-->>C: Proceed if authenticated
    M->>D: Forward request
    D->>DB: CreateOrganisation(OrgCreateRequest)
    DB-->>D: New Organisation record or error
    D->>C: Respond with JSON { org_id }

Possibly related PRs

  • publish ai summaries #1864: The changes in the main PR are related to the addition of new methods in the DiggerController struct, which are also referenced in the retrieved PR through modifications to the CreateDiggerBatch method that now accommodates new parameters relevant to AI summaries.

Poem

Oh, my whiskers tingle with delight,
New endpoints hop into view so bright.
Internal auth guards with a vigilant cheer,
Creating users and orgs, the journey is clear.
With SQL fields blooming on the database ground,
I, the coding rabbit, celebrate the sound! 🐇
Happy hops in every line of code found!

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

PR Summary

This PR introduces internal API endpoints for user and organization management, adding necessary database schema changes and controller implementations for user creation and organization management.

  • Added new CreateUserInternal and CreateOrgInternal endpoints in /backend/controllers/internal_users.go with basic auth protection
  • Modified User model in /backend/models/user.go to include Email, ExternalId, and OrgId fields
  • Added migration 20250220084846.sql with type mismatch between org_id (bigint) and model's OrgId (*uint)
  • Renamed WebhookAuth to InternalApiAuth in /backend/middleware/webhook.go with bearer token validation
  • Missing input validation and error handling in controller endpoints (e.g., email format, required fields)

💡 (2/5) Greptile learns from your feedback when you react with 👍/👎!

7 file(s) reviewed, 6 comment(s)
Edit PR Review Bot Settings | Greptile

@@ -222,7 +222,9 @@ func Bootstrap(templates embed.FS, diggerController controllers.DiggerController
runsApiGroup.POST("/:run_id/approve", controllers.ApproveRun)

// internal endpoints not meant to be exposed to public and protected behing webhook secret
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: 'behing' is misspelled in the comment

Suggested change
// internal endpoints not meant to be exposed to public and protected behing webhook secret
// internal endpoints not meant to be exposed to public and protected behind webhook secret

@@ -4,5 +4,8 @@ import "gorm.io/gorm"

type User struct {
gorm.Model
Username string `gorm:"uniqueIndex:idx_user"`
Email string
ExternalId string
Copy link
Contributor

Choose a reason for hiding this comment

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

style: ExternalId should likely have an index for faster lookups since it's used for external identity mapping

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

🧹 Nitpick comments (4)
backend/models/user.go (1)

7-10: Consider adding field validation and documentation.

The new fields look good, but consider adding:

  1. Field validation tags for email format.
  2. Field documentation to describe the purpose of each field.
  3. JSON tags for API serialization.

Apply this diff to improve the field definitions:

-	Email      string
-	ExternalId string
-	OrgId      *uint
-	Username   string `gorm:"uniqueIndex:idx_user"`
+	// Email is the user's primary email address
+	Email      string `gorm:"not null" json:"email" validate:"required,email"`
+	// ExternalId is the user's ID from the external authentication system
+	ExternalId string `gorm:"not null" json:"external_id" validate:"required"`
+	// OrgId is the optional ID of the organization this user belongs to
+	OrgId      *uint  `json:"org_id"`
+	// Username is a unique identifier for the user
+	Username   string `gorm:"uniqueIndex:idx_user" json:"username" validate:"required"`
backend/controllers/internal_users.go (1)

10-38: Enhance error handling and input validation.

The function logic is correct, but consider these improvements:

  1. Add input validation for required fields.
  2. Return appropriate HTTP status codes (e.g., 400 for invalid input).
  3. Add structured logging with request context.

Apply this diff to improve the implementation:

 func (d DiggerController) CreateOrgInternal(c *gin.Context) {
 	type OrgCreateRequest struct {
-		Name           string `json:"org_name"`
-		ExternalSource string `json:"external_source"`
-		ExternalId     string `json:"external_id"`
+		Name           string `json:"org_name" binding:"required"`
+		ExternalSource string `json:"external_source" binding:"required"`
+		ExternalId     string `json:"external_id" binding:"required"`
 	}

 	var request OrgCreateRequest
 	err := c.BindJSON(&request)
 	if err != nil {
-		log.Printf("Error binding JSON: %v", err)
-		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error binding JSON"})
+		log.Printf("Invalid request payload: %v", err)
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
 		return
 	}

 	name := request.Name
 	externalSource := request.ExternalSource
 	externalId := request.ExternalId

-	log.Printf("creating org for %v %v %v", name, externalSource, externalId)
+	log.Printf("Creating organization - name: %v, source: %v, id: %v", name, externalSource, externalId)
 	org, err := models.DB.CreateOrganisation(name, externalSource, externalId)
 	if err != nil {
 		log.Printf("Error creating org: %v", err)
 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating org"})
 		return
 	}

-	c.JSON(200, gin.H{"status": "success", "org_id": org.ID})
+	c.JSON(http.StatusCreated, gin.H{"status": "success", "org_id": org.ID})
 }
backend/models/storage.go (1)

1035-1044: Enhance error handling and add input validation.

The function logic is correct, but consider these improvements:

  1. Add input validation for required fields.
  2. Check for duplicate email/username before saving.
  3. Add structured logging with request context.

Apply this diff to improve the implementation:

 func (db *Database) CreateUser(email string, externalId string, orgId uint, username string) (*User, error) {
+	// Validate input
+	if email == "" || externalId == "" || username == "" {
+		return nil, fmt.Errorf("email, externalId, and username are required")
+	}
+
+	// Check for existing user
+	var existingUser User
+	if err := db.GormDB.Where("email = ? OR username = ?", email, username).First(&existingUser).Error; err == nil {
+		return nil, fmt.Errorf("user with email %s or username %s already exists", email, username)
+	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
+		return nil, fmt.Errorf("error checking for existing user: %v", err)
+	}
+
 	user := &User{Email: email, ExternalId: externalId, OrgId: &orgId, Username: username}
 	result := db.GormDB.Save(user)
 	if result.Error != nil {
-		log.Printf("Failed to create user: %v, error: %v\n", externalId, result.Error)
+		log.Printf("Failed to create user - email: %v, externalId: %v, error: %v\n", email, externalId, result.Error)
 		return nil, result.Error
 	}
-	log.Printf("User (id: %v) has been created successfully\n", externalId)
+	log.Printf("User created successfully - id: %v, email: %v, externalId: %v\n", user.ID, email, externalId)
 	return user, nil
 }
backend/migrations/20250220084846.sql (1)

1-3: Review Migration Syntax and Best Practices.
The migration correctly adds the new nullable columns "email", "external_id", and "org_id" to the "public"."users" table. Consider adding inline comments or documentation notes to explain the purpose of these fields for future maintainability. Additionally, if rollback support is required in your deployment process, you might want to create a corresponding down migration. For large tables in production, also verify that the single ALTER TABLE statement does not introduce extended lock times.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c758fb and bc55da2.

⛔ Files ignored due to path filters (1)
  • backend/migrations/atlas.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • backend/bootstrap/main.go (1 hunks)
  • backend/controllers/internal_users.go (1 hunks)
  • backend/middleware/webhook.go (1 hunks)
  • backend/migrations/20250220084846.sql (1 hunks)
  • backend/models/storage.go (1 hunks)
  • backend/models/user.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/middleware/webhook.go
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
🔇 Additional comments (1)
backend/bootstrap/main.go (1)

225-227: LGTM! Consistent use of internal API authentication.

The endpoints are correctly configured with the InternalApiAuth middleware, ensuring consistent authentication across internal APIs.

Comment on lines 40 to 76
func (d DiggerController) CreateUserInternal(c *gin.Context) {
type UserCreateRequest struct {
UserExternalId string `json:"external_id"`
UserEmail string `json:"email"`
OrgExternalId string `json:"external_org_id"`
}

var request UserCreateRequest
err := c.BindJSON(&request)
if err != nil {
log.Printf("Error binding JSON: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error binding JSON"})
return
}

extUserId := request.UserExternalId
userEmail := request.UserEmail
externalOrgId := request.OrgExternalId

org, err := models.DB.GetOrganisation(externalOrgId)
if err != nil {
log.Printf("Error retriving org: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retriving org"})
return
}

// for now using email for username since we want to deprecate that field
username := userEmail
user, err := models.DB.CreateUser(userEmail, extUserId, org.ID, username)
if err != nil {
log.Printf("Error creating user: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating user"})
return
}

c.JSON(200, gin.H{"status": "success", "user_id": user.ID})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling and reconsider username strategy.

The function logic is correct, but consider these improvements:

  1. Add input validation for required fields.
  2. Return appropriate HTTP status codes.
  3. Add structured logging.
  4. Reconsider using email as username, as it might need to change if the user's email changes.

Apply this diff to improve the implementation:

 func (d DiggerController) CreateUserInternal(c *gin.Context) {
 	type UserCreateRequest struct {
-		UserExternalId string `json:"external_id"`
-		UserEmail      string `json:"email"`
-		OrgExternalId  string `json:"external_org_id"`
+		UserExternalId string `json:"external_id" binding:"required"`
+		UserEmail      string `json:"email" binding:"required,email"`
+		OrgExternalId  string `json:"external_org_id" binding:"required"`
 	}

 	var request UserCreateRequest
 	err := c.BindJSON(&request)
 	if err != nil {
-		log.Printf("Error binding JSON: %v", err)
-		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error binding JSON"})
+		log.Printf("Invalid request payload: %v", err)
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
 		return
 	}

 	extUserId := request.UserExternalId
 	userEmail := request.UserEmail
 	externalOrgId := request.OrgExternalId

 	org, err := models.DB.GetOrganisation(externalOrgId)
 	if err != nil {
-		log.Printf("Error retriving org: %v", err)
-		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retriving org"})
+		log.Printf("Error retrieving org: %v", err)
+		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving org"})
 		return
 	}
+	if org == nil {
+		log.Printf("Organization not found: %v", externalOrgId)
+		c.JSON(http.StatusNotFound, gin.H{"error": "Organization not found"})
+		return
+	}

-	// for now using email for username since we want to deprecate that field
-	username := userEmail
+	// Generate a unique username based on external ID
+	username := fmt.Sprintf("user_%s", extUserId)
 	user, err := models.DB.CreateUser(userEmail, extUserId, org.ID, username)
 	if err != nil {
 		log.Printf("Error creating user: %v", err)
 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating user"})
 		return
 	}

-	c.JSON(200, gin.H{"status": "success", "user_id": user.ID})
+	c.JSON(http.StatusCreated, gin.H{"status": "success", "user_id": user.ID})
 }
📝 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
func (d DiggerController) CreateUserInternal(c *gin.Context) {
type UserCreateRequest struct {
UserExternalId string `json:"external_id"`
UserEmail string `json:"email"`
OrgExternalId string `json:"external_org_id"`
}
var request UserCreateRequest
err := c.BindJSON(&request)
if err != nil {
log.Printf("Error binding JSON: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error binding JSON"})
return
}
extUserId := request.UserExternalId
userEmail := request.UserEmail
externalOrgId := request.OrgExternalId
org, err := models.DB.GetOrganisation(externalOrgId)
if err != nil {
log.Printf("Error retriving org: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retriving org"})
return
}
// for now using email for username since we want to deprecate that field
username := userEmail
user, err := models.DB.CreateUser(userEmail, extUserId, org.ID, username)
if err != nil {
log.Printf("Error creating user: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating user"})
return
}
c.JSON(200, gin.H{"status": "success", "user_id": user.ID})
}
func (d DiggerController) CreateUserInternal(c *gin.Context) {
type UserCreateRequest struct {
UserExternalId string `json:"external_id" binding:"required"`
UserEmail string `json:"email" binding:"required,email"`
OrgExternalId string `json:"external_org_id" binding:"required"`
}
var request UserCreateRequest
err := c.BindJSON(&request)
if err != nil {
log.Printf("Invalid request payload: %v", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
return
}
extUserId := request.UserExternalId
userEmail := request.UserEmail
externalOrgId := request.OrgExternalId
org, err := models.DB.GetOrganisation(externalOrgId)
if err != nil {
log.Printf("Error retrieving org: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving org"})
return
}
if org == nil {
log.Printf("Organization not found: %v", externalOrgId)
c.JSON(http.StatusNotFound, gin.H{"error": "Organization not found"})
return
}
// Generate a unique username based on external ID
username := fmt.Sprintf("user_%s", extUserId)
user, err := models.DB.CreateUser(userEmail, extUserId, org.ID, username)
if err != nil {
log.Printf("Error creating user: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating user"})
return
}
c.JSON(http.StatusCreated, gin.H{"status": "success", "user_id": user.ID})
}

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
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: 0

🧹 Nitpick comments (3)
backend/models/user.go (3)

7-7: Add email format validation.

While the unique index is good, consider adding validation for email format using a validator tag.

-	Email      string `gorm:"uniqueIndex"`
+	Email      string `gorm:"uniqueIndex" validate:"required,email"`

9-9: Add foreign key constraint for OrgId.

Consider adding a foreign key constraint to ensure referential integrity with the organizations table.

-	OrgId      *uint
+	OrgId      *uint `gorm:"foreignKey:id;references:organizations"`

10-10: Use more specific index name.

The index name "idx_user" is too generic. Consider using a more specific name that indicates the indexed field.

-	Username   string `gorm:"uniqueIndex:idx_user"`
+	Username   string `gorm:"uniqueIndex:idx_username"`
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc55da2 and 0555513.

📒 Files selected for processing (2)
  • backend/controllers/internal_users.go (1 hunks)
  • backend/models/user.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/controllers/internal_users.go
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
🔇 Additional comments (1)
backend/models/user.go (1)

8-8: Add index to ExternalId field for faster lookups.

Since ExternalId is used for external identity mapping, it should be indexed for better query performance.

-	ExternalId string
+	ExternalId string `gorm:"index"`

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: 0

♻️ Duplicate comments (1)
backend/controllers/internal_users.go (1)

63-65: ⚠️ Potential issue

Add a return statement after handling organization retrieval error.

If there's an error retrieving the organization, the function continues to create a user with a potentially nil org, which can cause undefined behavior or runtime panics.

	if err != nil {
		log.Printf("Error retrieving org: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving org"})
+		return
	}
🧹 Nitpick comments (2)
backend/controllers/internal_users.go (2)

10-38: Consider returning 201 status and adding optional request validation.

While the logic here seems solid, returning http.StatusCreated would be more appropriate for newly created resources, rather than http.StatusOK. Additionally, consider validating the request fields (e.g., ensuring org_name isn't empty) to prevent downstream issues.


76-76: Return 201 for successful resource creation.

When creating a new user, return a 201 status to reflect that a new resource has been created:

- c.JSON(200, gin.H{"status": "success", "user_id": user.ID})
+ c.JSON(http.StatusCreated, gin.H{"status": "success", "user_id": user.ID})
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0555513 and d65d7d0.

⛔ Files ignored due to path filters (1)
  • backend/migrations/atlas.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • backend/bootstrap/main.go (1 hunks)
  • backend/controllers/internal_users.go (1 hunks)
  • backend/migrations/20250220172054.sql (1 hunks)
  • backend/migrations/20250220172321.sql (1 hunks)
  • backend/migrations/20250220173053.sql (1 hunks)
  • backend/migrations/20250220173439.sql (1 hunks)
  • backend/migrations/20250221044813.sql (1 hunks)
  • backend/models/orgs.go (1 hunks)
  • backend/models/storage.go (1 hunks)
  • backend/models/user.go (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • backend/migrations/20250220173053.sql
  • backend/migrations/20250220172321.sql
  • backend/migrations/20250221044813.sql
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/bootstrap/main.go
  • backend/models/storage.go
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
🔇 Additional comments (6)
backend/controllers/internal_users.go (1)

49-53: Use 400 Bad Request instead of 500 Internal Server Error for JSON binding failures.

Similar to a previously noted comment, responding with 500 for a client-side payload error can be misleading. Use a 4xx status code to indicate request errors, for example:

- c.JSON(http.StatusInternalServerError, gin.H{"error": "Error binding JSON"})
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
backend/models/user.go (1)

7-13: Looks great.

All newly added fields and unique constraints appear correctly set up for typical usage.

backend/models/orgs.go (1)

10-10:

❓ Verification inconclusive

Verify removal of the unique constraint on organisation name.

Changing this to a non-unique index allows multiple orgs to have the same name, which could be intentional or could introduce confusion. Please confirm this design choice.


🏁 Script executed:

#!/bin/bash
# Searching for references to organisation indexing in the codebase to see if anything relies on uniqueness
rg "Index:idx_organisation|uniqueIndex:idx_organisation" -A 3

Length of output: 340


Design Confirmation on Organisation Name Unique Constraint

  • The output confirms that the Name field now uses a non-unique index:
    Name           string `gorm:"Index:idx_organisation"`
    
  • Unlike the external source fields, which continue to use unique constraints via uniqueIndex:idx_external_source, the unique constraint for the organisation name has been removed.
  • Please confirm that allowing duplicate organisation names is an intentional design decision.
backend/migrations/20250220172054.sql (1)

1-2: SQL Migration: Adding External Source Column

The migration correctly adds an external_source column of type text to the "public"."users" table, allowing for null values. Verify that the data type is appropriate for the external data you expect and that the application layer (models, controllers) is updated to handle this new field.

backend/migrations/20250220173439.sql (2)

1-2: SQL Migration: Renaming Column

Renaming "org_id" to "organisation_id" enhances clarity and consistency. Ensure that this change is fully propagated throughout the codebase, including model definitions and any SQL queries that reference this column.


3-4: SQL Migration: Adding Foreign Key Constraint

Adding the foreign key constraint "fk_users_organisation" on "organisation_id" is a good step for maintaining referential integrity with the "public"."organisations" table. Please verify that:

  • The "organisations" table exists with an indexed and primary key "id".
  • The chosen referential actions (ON UPDATE NO ACTION ON DELETE NO ACTION) align with your application's data handling and integrity needs.

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: 2

♻️ Duplicate comments (2)
backend/controllers/internal_users.go (2)

61-64: ⚠️ Potential issue

Fix incorrect error status code.

JSON binding errors should return 400 Bad Request, not 500 Internal Server Error.

Apply this diff to fix the status code:

 	if err != nil {
 		log.Printf("Error binding JSON: %v", err)
-		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error binding JSON"})
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Error binding JSON"})
 		return
 	}

72-76: ⚠️ Potential issue

Add missing return statement and nil check.

The function continues execution after returning an error response, and it doesn't check if the organization exists.

Apply this diff to fix the error handling:

 	org, err := models.DB.GetOrganisation(externalOrgId)
 	if err != nil {
 		log.Printf("Error retrieving org: %v", err)
 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving org"})
+		return
+	}
+	if org == nil {
+		log.Printf("Organization not found: %v", externalOrgId)
+		c.JSON(http.StatusNotFound, gin.H{"error": "Organization not found"})
+		return
 	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d65d7d0 and c18e787.

📒 Files selected for processing (2)
  • backend/bootstrap/main.go (1 hunks)
  • backend/controllers/internal_users.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build
  • GitHub Check: Build
🔇 Additional comments (2)
backend/controllers/internal_users.go (1)

87-87: Use http.StatusOK instead of hard-coded status code.

For consistency and maintainability, use the standard HTTP status constants.

Apply this diff to use the constant:

-	c.JSON(200, gin.H{"status": "success", "user_id": user.ID})
+	c.JSON(http.StatusOK, gin.H{"status": "success", "user_id": user.ID})
backend/bootstrap/main.go (1)

224-229: LGTM! The internal endpoints implementation is secure and well-structured.

The implementation:

  1. Uses environment variable to control access to internal endpoints
  2. Consistently applies InternalApiAuth middleware
  3. Groups related internal endpoints together

Comment on lines +10 to +15
func (d DiggerController) UpsertOrgInternal(c *gin.Context) {
type OrgCreateRequest struct {
Name string `json:"org_name"`
ExternalSource string `json:"external_source"`
ExternalId string `json:"external_id"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for required fields.

The request structure should enforce required fields using binding tags to ensure data integrity.

Apply this diff to add input validation:

 type OrgCreateRequest struct {
-		Name           string `json:"org_name"`
-		ExternalSource string `json:"external_source"`
-		ExternalId     string `json:"external_id"`
+		Name           string `json:"org_name" binding:"required"`
+		ExternalSource string `json:"external_source" binding:"required"`
+		ExternalId     string `json:"external_id" binding:"required"`
 }
📝 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
func (d DiggerController) UpsertOrgInternal(c *gin.Context) {
type OrgCreateRequest struct {
Name string `json:"org_name"`
ExternalSource string `json:"external_source"`
ExternalId string `json:"external_id"`
}
func (d DiggerController) UpsertOrgInternal(c *gin.Context) {
type OrgCreateRequest struct {
Name string `json:"org_name" binding:"required"`
ExternalSource string `json:"external_source" binding:"required"`
ExternalId string `json:"external_id" binding:"required"`
}
// Rest of the implementation...
}

Comment on lines +33 to +35
log.Printf("Error while retriving org: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating org"})
return
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

Fix error message inconsistency.

The error message doesn't match the actual error scenario. The message says "Error creating org" but the error occurs while retrieving the organization.

Apply this diff to fix the error message:

-		log.Printf("Error while retriving org: %v", err)
-		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating org"})
+		log.Printf("Error while retrieving org: %v", err)
+		c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving org"})
📝 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
log.Printf("Error while retriving org: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating org"})
return
log.Printf("Error while retrieving org: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving org"})
return

@motatoes motatoes merged commit f5f7f58 into develop Feb 21, 2025
12 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 24, 2025
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