Skip to content

feat/drift fields #2018

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

Merged
merged 4 commits into from
Jul 11, 2025
Merged

feat/drift fields #2018

merged 4 commits into from
Jul 11, 2025

Conversation

motatoes
Copy link
Contributor

@motatoes motatoes commented Jul 11, 2025

  • update fields
  • add drift settings and drift updates per project

Summary by CodeRabbit

  • New Features

    • Added API endpoints to view and update organization drift settings.
    • Added API endpoint to update project drift settings.
    • Extended project and organization data with new drift-related fields, including drift status, last drift check, and drift webhook configuration.
  • Database Changes

    • Added new columns to organizations and projects for managing and tracking drift detection status and settings.

Copy link
Contributor

coderabbitai bot commented Jul 11, 2025

Walkthrough

This update introduces new API endpoints for retrieving and updating drift-related settings at both the organization and project levels. It extends the database schema and data models to support drift detection features, including new fields for drift status, scheduling, and webhook integration. Controllers and serialization logic are updated accordingly.

Changes

File(s) Change Summary
backend/bootstrap/main.go Added new API routes for organization and project drift settings.
backend/controllers/orgs.go Added handlers for getting and updating organization drift settings.
backend/controllers/projects.go Added handler for updating project drift settings.
backend/models/orgs.go Extended Organisation and Project structs with drift-related fields; updated JSON serialization.
backend/migrations/20250710234046.sql Added drift-related columns to "projects" table.
backend/migrations/20250711030148.sql, ...11030248.sql, ...11030323.sql Added and defaulted drift-related columns in "organisations" table.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant Controllers
    participant DB

    Client->>API: GET /api/orgs/settings/
    API->>Controllers: GetOrgSettingsApi
    Controllers->>DB: Query organisation by external_id & source
    DB-->>Controllers: Organisation record
    Controllers-->>API: JSON (drift_enabled, drift_cron_tab, drift_webhook_url)
    API-->>Client: Response

    Client->>API: PUT /api/orgs/settings/ (JSON body)
    API->>Controllers: UpdateOrgSettingsApi
    Controllers->>DB: Query & update organisation
    DB-->>Controllers: Save result
    Controllers-->>API: 200 OK (empty JSON)
    API-->>Client: Response

    Client->>API: PUT /api/projects/:project_id/ (JSON body)
    API->>Controllers: UpdateProjectApi
    Controllers->>DB: Query organisation, then project
    Controllers->>DB: Update project drift_enabled
    DB-->>Controllers: Save result
    Controllers-->>API: Updated project JSON
    API-->>Client: Response
Loading

Poem

A rabbit hopped through code so neat,
Adding drift to every seat.
Orgs and projects now can say,
"Check my drift, update today!"
With webhooks, crons, and status new,
The schema grew, the models too—
This bunny’s work is never through! 🐇✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0333607 and 8d30649.

📒 Files selected for processing (1)
  • backend/models/orgs.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/models/orgs.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Security Check
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/drift-fields

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
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @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

Implements comprehensive drift detection functionality across organizations and projects with configurable settings and status tracking.

  • Added project-level drift tracking in backend/models/orgs.go with DriftStatus enum (new/no/acknowledged) and plan storage
  • Introduced organization-wide drift settings in backend/controllers/orgs.go with webhook URLs and cron scheduling
  • Added migrations (20250710234046.sql, 20250711030148.sql) for drift configuration storage with appropriate defaults
  • Implemented new API endpoints in backend/bootstrap/main.go for managing drift settings at both org and project levels

9 files reviewed, 10 comments
Edit PR Review Bot Settings | Greptile

@@ -17,6 +21,72 @@ type TenantCreatedEvent struct {
Name string `json:"name,omitempty"`
}

func GetOrgSettingsApi(c *gin.Context) {
// TODO
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Remove TODO comment - implementation is complete

Suggested change
// TODO
func GetOrgSettingsApi(c *gin.Context) {

@@ -0,0 +1,2 @@
-- Modify "organisations" table
ALTER TABLE "public"."organisations" ADD COLUMN "drift_enabled" boolean NULL;
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Consider setting a default value of FALSE for drift_enabled to ensure explicit opt-in for drift detection

@@ -0,0 +1,2 @@
-- Modify "organisations" table
ALTER TABLE "public"."organisations" ADD COLUMN "drift_enabled" boolean NULL;
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: This column should not be nullable since it represents a feature flag. Boolean columns should typically have a definite true/false value

DriftCronTab string `json:"drift_cron_tab"`
DriftWebhookUrl string `json:"drift_webhook_url"`
}
err = json.NewDecoder(c.Request.Body).Decode(&reqBody)
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Consider using c.ShouldBindJSON() for consistency with other endpoints in the codebase

}

func UpdateOrgSettingsApi(c *gin.Context) {
// TODO
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Remove TODO comment - implementation is complete

Suggested change
// TODO
func UpdateOrgSettingsApi(c *gin.Context) {

@@ -0,0 +1,2 @@
-- Modify "projects" table
ALTER TABLE "public"."projects" ADD COLUMN "drift_enabled" boolean NULL DEFAULT false, ADD COLUMN "drift_status" text NULL DEFAULT 'no drift', ADD COLUMN "latest_drift_check" timestamptz NULL, ADD COLUMN "drift_terraform_plan" text NULL;
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Consider adding CHECK constraint to limit drift_status to specific valid values (e.g., 'no drift', 'drift detected', 'checking')

@@ -0,0 +1,2 @@
-- Modify "projects" table
ALTER TABLE "public"."projects" ADD COLUMN "drift_enabled" boolean NULL DEFAULT false, ADD COLUMN "drift_status" text NULL DEFAULT 'no drift', ADD COLUMN "latest_drift_check" timestamptz NULL, ADD COLUMN "drift_terraform_plan" text NULL;
Copy link
Contributor

Choose a reason for hiding this comment

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

style: The drift-related fields currently allow NULL. Add NOT NULL constraints for critical fields like drift_enabled and drift_status since they have defaults

Suggested change
ALTER TABLE "public"."projects" ADD COLUMN "drift_enabled" boolean NULL DEFAULT false, ADD COLUMN "drift_status" text NULL DEFAULT 'no drift', ADD COLUMN "latest_drift_check" timestamptz NULL, ADD COLUMN "drift_terraform_plan" text NULL;
ALTER TABLE "public"."projects" ADD COLUMN "drift_enabled" boolean NOT NULL DEFAULT false, ADD COLUMN "drift_status" text NOT NULL DEFAULT 'no drift', ADD COLUMN "latest_drift_check" timestamptz NULL, ADD COLUMN "drift_terraform_plan" text NULL;

Comment on lines 77 to 81
type DriftStatus string

var DriftStatusNewDrift = "new drift"
var DriftStatusNoDrift = "no drift"
var DriftStatusAcknowledgeDrift = "acknowledged drift"
Copy link
Contributor

Choose a reason for hiding this comment

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

style: DriftStatus constants should use const declarations instead of var for type safety and immutability.

Suggested change
type DriftStatus string
var DriftStatusNewDrift = "new drift"
var DriftStatusNoDrift = "no drift"
var DriftStatusAcknowledgeDrift = "acknowledged drift"
type DriftStatus string
const (
DriftStatusNewDrift DriftStatus = "new drift"
DriftStatusNoDrift DriftStatus = "no drift"
DriftStatusAcknowledgeDrift DriftStatus = "acknowledged drift"
)

@@ -109,6 +126,10 @@ func (p *Project) MapToJsonStruct() interface{} {
OrganisationID: p.OrganisationID,
OrganisationName: p.Organisation.Name,
RepoFullName: p.RepoFullName,
DriftEnabled: p.DriftEnabled,
DriftStatus: string(p.DriftStatus),
LatestDriftCheck: p.LatestDriftCheck.String(),
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Direct String() call on time.Time may produce inconsistent formats. Consider using a standardized time format.

err = models.DB.GormDB.Save(&project).Error
if err != nil {
slog.Error("Error updating project", "organisationId", organisationId, "orgId", org.ID, "error", err)
c.String(http.StatusInternalServerError, "Unknown error occurred while fetching database")
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Error message suggests fetching database when it's actually an error updating

Suggested change
c.String(http.StatusInternalServerError, "Unknown error occurred while fetching database")
c.String(http.StatusInternalServerError, "Unknown error occurred while updating database")

Copy link

delve-auditor bot commented Jul 11, 2025

No security or compliance issues detected. Reviewed everything up to 8d30649.

Security Overview
  • 🔎 Scanned files: 9 changed file(s)
Detected Code Changes
Change Type Relevant files
Enhancement ► main.go
    Add new org settings and project update API routes
► orgs.go
    Add drift settings endpoints for organizations
► projects.go
    Add project drift update endpoint
Configuration changes ► migrations/*.sql
    Add drift-related fields to projects and organisations tables
► models/orgs.go
    Add drift-related model fields and types
Other ► docs/
    Remove remote-jobs documentation and related images

Reply to this PR with @delve-auditor followed by a description of what change you want and we'll auto-submit a change to this PR to implement it.

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

🧹 Nitpick comments (5)
backend/controllers/projects.go (2)

92-100: Consider adding input validation for the request body.

While JSON decoding error handling is present, consider adding validation to ensure the request body contains expected fields and values.

 var reqBody struct {
   DriftEnabled bool `json:"drift_enabled"`
 }
 err = json.NewDecoder(c.Request.Body).Decode(&reqBody)
 if err != nil {
   slog.Error("Error decoding request body", "error", err)
   c.String(http.StatusBadRequest, "Error decoding request body")
   return
 }
+
+// Optional: Add validation if needed
+// if reqBody.DriftEnabled != true && reqBody.DriftEnabled != false {
+//   c.String(http.StatusBadRequest, "Invalid drift_enabled value")
+//   return
+// }

123-123: Consider security implications of returning the full project object.

The function returns the entire project object, which might expose sensitive information. Consider returning only the updated fields or a success message.

-c.JSON(http.StatusOK, project)
+c.JSON(http.StatusOK, gin.H{
+  "drift_enabled": project.DriftEnabled,
+  "message": "Project updated successfully",
+})
backend/controllers/orgs.go (2)

24-47: Address the TODO comment and consider input validation.

The function structure is solid with proper error handling and logging. However, the TODO comment should be addressed or removed.

-// TODO
+// Get organisation drift settings

Do you want me to help implement any specific functionality mentioned in the TODO, or should this comment be removed?


50-50: Remove or address the TODO comment.

The TODO comment should be replaced with a proper description or removed if no additional work is needed.

-// TODO
+// Update organisation drift settings
backend/models/orgs.go (1)

115-132: Consider using ISO 8601 format for timestamp serialization.

The drift fields are properly included in the JSON mapping. However, consider using a more standard time format for API consumers.

 	DriftEnabled          bool   `json:"drift_enabled"`
 	DriftStatus           string `json:"drift_status"`
-	LatestDriftCheck      string `json:"latest_drift_check"`
+	LatestDriftCheck      string `json:"latest_drift_check"`
 	DriftTerraformPlan    string `json:"drift_terraform_plan"`

And in the struct initialization:

 	DriftEnabled:          p.DriftEnabled,
 	DriftStatus:           string(p.DriftStatus),
-	LatestDriftCheck:      p.LatestDriftCheck.String(),
+	LatestDriftCheck:      p.LatestDriftCheck.Format(time.RFC3339),
 	DriftTerraformPlan:    p.DriftTerraformPlan,

This provides a more standard ISO 8601 format that's easier to parse by API consumers.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c565c98 and 7bab0d6.

⛔ Files ignored due to path filters (1)
  • backend/migrations/atlas.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • backend/bootstrap/main.go (1 hunks)
  • backend/controllers/orgs.go (2 hunks)
  • backend/controllers/projects.go (1 hunks)
  • backend/migrations/20250710234046.sql (1 hunks)
  • backend/migrations/20250711030148.sql (1 hunks)
  • backend/migrations/20250711030248.sql (1 hunks)
  • backend/migrations/20250711030323.sql (1 hunks)
  • backend/models/orgs.go (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
backend/controllers/projects.go (1)
Learnt from: motatoes
PR: diggerhq/digger#1864
File: backend/controllers/projects.go:672-678
Timestamp: 2024-12-30T09:19:58.162Z
Learning: In file `backend/controllers/projects.go`, the user clarified that the AI summary token is considered non-critical, so they prefer not to validate it.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Build
  • GitHub Check: Security Check
🔇 Additional comments (9)
backend/migrations/20250710234046.sql (1)

1-2: Migration looks good with proper defaults and data types.

The migration correctly adds drift-related fields to the projects table with sensible defaults and appropriate data types. The nullable fields ensure backwards compatibility.

backend/migrations/20250711030323.sql (1)

1-2: Migration dependency order verified

The prior migration 20250711030248.sql is present and will run before 20250711030323.sql, so the drift_enabled column exists when this default is applied. No further action needed.

backend/migrations/20250711030248.sql (1)

1-2: Clean migration adding drift_enabled column.

The migration properly adds the drift_enabled boolean column as nullable, maintaining backwards compatibility. The default value is appropriately set in a subsequent migration.

backend/migrations/20250711030148.sql (1)

1-2: Migration correctly adds webhook and cron scheduling fields.

The migration properly adds drift configuration fields with appropriate defaults. The cron expression '0 0 * * *' (daily at midnight) is a sensible default for drift checking.

backend/bootstrap/main.go (2)

238-238: Approve addition of PUT /:project_id/ endpoint

The new PUT /:project_id/ route is consistent with existing API patterns, and the UpdateProjectApi handler is correctly implemented in backend/controllers/projects.go. No further changes required.


228-231: Controller implementations confirmed

Both GetOrgSettingsApi and UpdateOrgSettingsApi are defined and implemented in backend/controllers/orgs.go (lines 24–47 and 49–89). No further changes needed—approving these route additions.

backend/controllers/projects.go (1)

74-124: Well-structured API handler with good error handling.

The function follows established patterns in the codebase and includes proper error handling, logging, and middleware integration. The implementation is clean and consistent.

backend/models/orgs.go (2)

14-16: Well-structured drift fields with appropriate GORM tags.

The new drift-related fields are properly defined with sensible defaults and follow GORM conventions.


90-93: Well-integrated drift fields with proper GORM configuration.

The new drift-related fields are properly integrated into the Project struct with appropriate defaults and GORM tags.

Comment on lines 49 to 89
func UpdateOrgSettingsApi(c *gin.Context) {
// TODO
organisationId := c.GetString(middleware.ORGANISATION_ID_KEY)
organisationSource := c.GetString(middleware.ORGANISATION_SOURCE_KEY)

var org models.Organisation
err := models.DB.GormDB.Where("external_id = ? AND external_source = ?", organisationId, organisationSource).First(&org).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
slog.Info("Organisation not found", "organisationId", organisationId, "source", organisationSource)
c.String(http.StatusNotFound, "Could not find organisation: "+organisationId)
} else {
slog.Error("Error fetching organisation", "organisationId", organisationId, "source", organisationSource, "error", err)
c.String(http.StatusInternalServerError, "Error fetching organisation")
}
return
}
var reqBody struct {
DriftEnabled bool `json:"drift_enabled"`
DriftCronTab string `json:"drift_cron_tab"`
DriftWebhookUrl string `json:"drift_webhook_url"`
}
err = json.NewDecoder(c.Request.Body).Decode(&reqBody)
if err != nil {
slog.Error("Error decoding request body", "error", err)
c.String(http.StatusBadRequest, "Error decoding request body")
return
}

org.DriftEnabled = reqBody.DriftEnabled
org.DriftCronTab = reqBody.DriftCronTab
org.DriftWebhookUrl = reqBody.DriftWebhookUrl
err = models.DB.GormDB.Save(&org).Error
if err != nil {
slog.Error("Error saving organisation", "organisationId", organisationId, "source", organisationSource, "error", err)
c.String(http.StatusInternalServerError, "Error saving organisation")
return
}

c.JSON(http.StatusOK, gin.H{})
}
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

Address TODO comment and add input validation for cron expression and webhook URL.

The function structure is good, but consider adding validation for the cron expression format and webhook URL.

+import (
+  "net/url"
+  "regexp"
+)

 var reqBody struct {
   DriftEnabled    bool   `json:"drift_enabled"`
   DriftCronTab    string `json:"drift_cron_tab"`
   DriftWebhookUrl string `json:"drift_webhook_url"`
 }
 err = json.NewDecoder(c.Request.Body).Decode(&reqBody)
 if err != nil {
   slog.Error("Error decoding request body", "error", err)
   c.String(http.StatusBadRequest, "Error decoding request body")
   return
 }

+// Validate cron expression format (basic validation)
+if reqBody.DriftCronTab != "" {
+  cronRegex := regexp.MustCompile(`^(\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+) (\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+) (\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+) (\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+) (\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+)$`)
+  if !cronRegex.MatchString(reqBody.DriftCronTab) {
+    c.String(http.StatusBadRequest, "Invalid cron expression format")
+    return
+  }
+}
+
+// Validate webhook URL format
+if reqBody.DriftWebhookUrl != "" {
+  if _, err := url.Parse(reqBody.DriftWebhookUrl); err != nil {
+    c.String(http.StatusBadRequest, "Invalid webhook URL format")
+    return
+  }
+}
📝 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 UpdateOrgSettingsApi(c *gin.Context) {
// TODO
organisationId := c.GetString(middleware.ORGANISATION_ID_KEY)
organisationSource := c.GetString(middleware.ORGANISATION_SOURCE_KEY)
var org models.Organisation
err := models.DB.GormDB.Where("external_id = ? AND external_source = ?", organisationId, organisationSource).First(&org).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
slog.Info("Organisation not found", "organisationId", organisationId, "source", organisationSource)
c.String(http.StatusNotFound, "Could not find organisation: "+organisationId)
} else {
slog.Error("Error fetching organisation", "organisationId", organisationId, "source", organisationSource, "error", err)
c.String(http.StatusInternalServerError, "Error fetching organisation")
}
return
}
var reqBody struct {
DriftEnabled bool `json:"drift_enabled"`
DriftCronTab string `json:"drift_cron_tab"`
DriftWebhookUrl string `json:"drift_webhook_url"`
}
err = json.NewDecoder(c.Request.Body).Decode(&reqBody)
if err != nil {
slog.Error("Error decoding request body", "error", err)
c.String(http.StatusBadRequest, "Error decoding request body")
return
}
org.DriftEnabled = reqBody.DriftEnabled
org.DriftCronTab = reqBody.DriftCronTab
org.DriftWebhookUrl = reqBody.DriftWebhookUrl
err = models.DB.GormDB.Save(&org).Error
if err != nil {
slog.Error("Error saving organisation", "organisationId", organisationId, "source", organisationSource, "error", err)
c.String(http.StatusInternalServerError, "Error saving organisation")
return
}
c.JSON(http.StatusOK, gin.H{})
}
// Make sure to import "net/url" and "regexp" at the top of the file.
func UpdateOrgSettingsApi(c *gin.Context) {
// TODO
organisationId := c.GetString(middleware.ORGANISATION_ID_KEY)
organisationSource := c.GetString(middleware.ORGANISATION_SOURCE_KEY)
var org models.Organisation
err := models.DB.GormDB.
Where("external_id = ? AND external_source = ?", organisationId, organisationSource).
First(&org).
Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
slog.Info("Organisation not found", "organisationId", organisationId, "source", organisationSource)
c.String(http.StatusNotFound, "Could not find organisation: "+organisationId)
} else {
slog.Error("Error fetching organisation", "organisationId", organisationId, "source", organisationSource, "error", err)
c.String(http.StatusInternalServerError, "Error fetching organisation")
}
return
}
var reqBody struct {
DriftEnabled bool `json:"drift_enabled"`
DriftCronTab string `json:"drift_cron_tab"`
DriftWebhookUrl string `json:"drift_webhook_url"`
}
err = json.NewDecoder(c.Request.Body).Decode(&reqBody)
if err != nil {
slog.Error("Error decoding request body", "error", err)
c.String(http.StatusBadRequest, "Error decoding request body")
return
}
// Validate cron expression format (basic validation)
if reqBody.DriftCronTab != "" {
cronRegex := regexp.MustCompile(`^(\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+) (\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+) (\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+) (\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+) (\*|[0-9]+|\*/[0-9]+|[0-9]+-[0-9]+)$`)
if !cronRegex.MatchString(reqBody.DriftCronTab) {
c.String(http.StatusBadRequest, "Invalid cron expression format")
return
}
}
// Validate webhook URL format
if reqBody.DriftWebhookUrl != "" {
if _, err := url.Parse(reqBody.DriftWebhookUrl); err != nil {
c.String(http.StatusBadRequest, "Invalid webhook URL format")
return
}
}
org.DriftEnabled = reqBody.DriftEnabled
org.DriftCronTab = reqBody.DriftCronTab
org.DriftWebhookUrl = reqBody.DriftWebhookUrl
err = models.DB.GormDB.Save(&org).Error
if err != nil {
slog.Error("Error saving organisation", "organisationId", organisationId, "source", organisationSource, "error", err)
c.String(http.StatusInternalServerError, "Error saving organisation")
return
}
c.JSON(http.StatusOK, gin.H{})
}
🤖 Prompt for AI Agents
In backend/controllers/orgs.go around lines 49 to 89, the UpdateOrgSettingsApi
function lacks validation for the cron expression and webhook URL inputs. Add
validation logic after decoding the request body to verify that DriftCronTab is
a valid cron expression and DriftWebhookUrl is a properly formatted URL. If
validation fails, respond with an appropriate HTTP 400 error and message. This
ensures only valid data is saved to the organisation record.

@motatoes motatoes merged commit 64a6f0d into develop Jul 11, 2025
13 checks passed
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