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: Add ability to lock detections to protect them from cleanup #424

Merged
merged 9 commits into from
Feb 1, 2025

Conversation

tphakala
Copy link
Owner

@tphakala tphakala commented Feb 1, 2025

This PR adds functionality to lock important detections to prevent them from being deleted during regular cleanup operations.

- Implemented lock/unlock feature for detections
- Added conditional rendering for delete and lock actions
- Updated Alpine.js state management to track detection lock status
- Introduced new event listeners for lock/unlock state changes
- Implemented lock management methods in datastore interfaces
- Added NoteLock model to support note locking
- Updated database migration to include NoteLock
- Added methods for locking, unlocking, and checking note lock status
- Modified note retrieval methods to preload and populate lock information
- Added lock status check before note deletion
- Excluded locked notes from clips qualifying for removal
- Introduced lock detection checkbox in review modal
- Added dynamic display of lock section based on detection verification
- Implemented client-side toggle for lock detection visibility
- Provided explanatory text for detection locking functionality
- Refactored ReviewDetection handler to support more robust comment and review processing
- Added support for locking detections during review
- Implemented retry mechanism for database transactions to handle SQLite locking
- Created separate methods for processing comments and reviews
- Added new LockDetection handler for explicit note locking/unlocking
- Improved error handling and notification mechanisms
- Added debug logging to processReview method for better traceability
- Enhanced review modal with Alpine.js for dynamic lock state handling
- Implemented client-side prevention of false positive marking for locked detections
- Added lock state synchronization between server and client
- Improved error handling and user feedback for locked detection scenarios
- Simplified lock/unlock confirmation modal text
Copy link
Contributor

coderabbitai bot commented Feb 1, 2025

Walkthrough

This pull request introduces note locking functionality across multiple layers of the application. New methods are added to the datastore interface for locking, unlocking, and querying note locks, and the underlying data model is updated with a NoteLock struct and associated fields. Database migrations now include the new lock entity, and deletion and retrieval operations check the lock status. In the HTTP controller, detection review handlers are refactored to support locking operations, and a new POST endpoint is added. Additionally, front-end templates have been updated to manage lock states and UI interactions for detection locking.

Changes

File(s) Change Summary
internal/datastore/interfaces.go,
internal/datastore/manage.go,
internal/datastore/model.go
Introduced note locking methods (LockNote, UnlockNote, GetNoteLock, IsNoteLocked) to the datastore interface, added a new NoteLock struct and fields in the Note model, and updated auto-migration to include the NoteLock entity. Related note retrieval and deletion logic now account for lock status.
internal/httpcontroller/handlers/detections.go,
internal/httpcontroller/routes.go
Refactored detection review handling by separating comment processing and review logic, added a new LockDetection handler for managing lock state, and introduced a POST /detections/lock endpoint with authentication to process lock/unlock actions.
views/elements/actionMenu.html,
views/elements/reviewModal.html
Updated front-end templates to incorporate a new isLocked property and event handling in Alpine.js components. The action menu and review modal UI now adjust available actions and inputs based on the detection’s lock status, including conditional display of delete or lock options.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant UI as Web UI (HTML/Alpine.js)
    participant HT as HTTP Controller
    participant DS as DataStore
    participant DB as Database

    U->>UI: Initiates lock/unlock action
    UI->>HT: Sends POST request to /detections/lock
    HT->>DS: Calls LockDetection / processReview
    DS->>DB: Queries/updates NoteLock and Note fields
    DB-->>DS: Returns lock status result
    DS-->>HT: Delivers processing outcome
    HT-->>UI: Sends updated lock state response
    UI->>UI: Updates UI state based on event triggers
Loading

Possibly related PRs

Poem

I’m a rabbit of code, hopping in delight,
With locks in the datastore, my world is bright.
Each note now guarded, secure and sound,
In every commit, joy is found.
I twitch my whiskers at features so keen—
A locked-up tale in every line of the scene!
🐇💻

✨ 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

@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 (6)
internal/datastore/interfaces.go (4)

169-170: Consider potential performance overhead.

Preloading multiple relationships (Review, Lock, Comments) can be efficient for single-note retrievals, but watch out for possible performance impacts if this query is used in loops or high-volume endpoints.


181-183: Ensure test coverage for the new Locked field.

Confirm that unit tests account for various lock states to maintain correctness.

Would you like help creating tests that verify the Locked field behavior across different scenarios?


735-759: Consider a unique constraint for lock enforcement.

The upsert approach works, but multiple concurrent lock attempts could overwrite each other. A unique database constraint on note_id would prevent multiple locks from coexisting unintentionally.

Below is an illustrative snippet you might add in a migration (not necessarily inline here), ensuring only one lock per note:

 func (ds *DataStore) LockNote(noteID, description, lockedBy string) error {
     ...
+    // Example: enforce uniqueness in migrations or setup:
+    // db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_note_locks_note_id ON note_locks(note_id)")

     // Use upsert operation to either create or update the lock
     result := ds.DB.Where("note_id = ?", id).
     ...
 }

761-774: Logging unlock events.

The unlock logic is straightforward. Consider capturing who unlocked the note if you need auditing or tighter control.

Example diff:

 func (ds *DataStore) UnlockNote(noteID string) error {
     id, err := strconv.ParseUint(noteID, 10, 32)
     if err != nil {
         return fmt.Errorf("invalid note ID: %w", err)
     }

+    // Potentially store or log the user who performed the unlock,
+    // e.g. an "UnlockedBy" column or an audit trail.

     result := ds.DB.Where("note_id = ?", id).Delete(&NoteLock{})
     ...
 }
internal/httpcontroller/handlers/detections.go (2)

477-584: Potential concurrency concerns when overwriting locks.

The logic properly prevents marking locked notes as false positives but might overwrite an existing lock from another user if verified as “correct” and lockDetection is true. Consider verifying the lock owner if multi-tenant usage is a concern.


668-741: Consider user validation when toggling locks.

Lock/unlock logic is straightforward. If a user tries to unlock a note locked by someone else, you may want to enforce ownership checks or warnings, depending on business rules.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 66a618a and bad9b38.

📒 Files selected for processing (7)
  • internal/datastore/interfaces.go (10 hunks)
  • internal/datastore/manage.go (1 hunks)
  • internal/datastore/model.go (2 hunks)
  • internal/httpcontroller/handlers/detections.go (2 hunks)
  • internal/httpcontroller/routes.go (1 hunks)
  • views/elements/actionMenu.html (2 hunks)
  • views/elements/reviewModal.html (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (25)
internal/datastore/interfaces.go (12)

50-54: New lock management interface methods.

These methods broaden the store’s functionality to include locking operations. The signatures and parameters look consistent with the existing patterns.


195-203: Lock check before deletion.

Preventing deletion for locked notes aligns with the new feature requirements. Looks good.


301-305: Lock exclusion in the subquery.

Excluding locked notes from removal aligns with the locking logic. Implementation looks correct.


314-316: Lock exclusion in main query.

Similar to the subquery logic, this ensures locked notes stay protected.


374-374: Preload the Lock for species detections.

This helps retrieve lock information consistently.


389-389: Updated comment for clarity.

No concerns.


394-394: Populate the Locked field.

Initializing this virtual field is consistent with the existing approach in other endpoints.


406-406: Preload lock in GetLastDetections.

Good consistency across retrieval methods.


412-412: Populate virtual fields comment.

No action needed.


417-417: Setting the note’s Locked property.

Maintains consistency in marking the note’s lock state.


776-793: Retrieving the note lock.

Implementation correctly returns nil when no record is found, which is consistent with typical “no lock” semantics.


795-802: Check if a note is locked.

Straightforward logic that leverages GetNoteLock.

internal/httpcontroller/handlers/detections.go (2)

586-630: Unified review flow.

The refactored workflow clearly separates comment and review handling. The new approach is cleaner and seems logically sound.


413-475: Clarify handling of multiple existing comments.

Currently, only the first existing comment is updated. If multiple comments exist, others remain untouched. Verify this aligns with product requirements or consider updating all relevant comments.

Below is a script to locate other references to comment handling and confirm if multi-comment logic exists:

internal/datastore/manage.go (1)

27-27: Include NoteLock in migrations.

Ensures the lock schema is created. Straightforward change and crucial for new lock features.

internal/datastore/model.go (3)

29-29: LGTM! The Lock field is properly configured.

The one-to-one relationship with cascade delete behavior ensures that lock records are properly cleaned up when notes are deleted.


31-33: LGTM! Virtual fields are properly documented.

The virtual fields maintain compatibility with templates and provide a clean way to expose lock status.


74-82: LGTM! The NoteLock struct is well-designed.

The struct includes all necessary fields for tracking lock status:

  • ID and NoteID for proper relationships
  • LockedAt for timestamp tracking
  • Description for lock reason documentation
  • LockedBy for ownership tracking
internal/httpcontroller/routes.go (1)

131-132: LGTM! The lock route is properly configured.

The route is:

  • Using POST method appropriately for state modification
  • Protected by authentication middleware
  • Grouped with other detection-related routes
views/elements/reviewModal.html (3)

49-60: LGTM! Alpine.js state management is well-implemented.

The component:

  • Properly initializes lock state from server data
  • Sets up event listeners for lock state changes
  • Cleans up event listeners on component initialization

77-89: LGTM! Lock-related UI changes are user-friendly.

The implementation:

  • Properly disables false positive option when locked
  • Shows clear warning message explaining why it's disabled
  • Uses appropriate styling for disabled state

91-101: LGTM! Lock detection section is well-designed.

The section:

  • Only shows for correct detections
  • Uses checkbox for intuitive toggling
  • Provides clear explanation of locking purpose
views/elements/actionMenu.html (3)

6-19: LGTM! Lock state management is properly implemented.

The implementation:

  • Initializes lock state from server data
  • Sets up event listeners for state changes
  • Maintains consistency with other state management

92-125: LGTM! Lock/unlock action is well-designed.

The implementation:

  • Uses appropriate confirmation dialogs
  • Provides clear explanations of lock/unlock effects
  • Uses intuitive icons for lock states

129-163: LGTM! Delete action is properly secured.

The implementation:

  • Only shows delete option when detection is unlocked
  • Maintains existing delete functionality
  • Uses appropriate warning styling

- Removed unnecessary fields from NoteLock model
- Simplified LockNote method signature
- Removed description and lockedBy fields from lock mechanism
- Updated datastore interface to reflect simplified locking approach
- Removed username parameter from processReview method
- Simplified review status handling in processReview
- Updated review modal to use Alpine.js for dynamic review status management
- Improved lock state and review status visibility logic
- Removed redundant username extraction in handlers
- Simplified lock detection implementation
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 (2)
internal/datastore/interfaces.go (1)

735-800: Consider adding retry mechanism for database locks.

The lock management implementations handle errors well but could benefit from retry logic for database locks, similar to other methods in the file.

 func (ds *DataStore) LockNote(noteID string) error {
     id, err := strconv.ParseUint(noteID, 10, 32)
     if err != nil {
         return fmt.Errorf("invalid note ID: %w", err)
     }

+    maxRetries := 5
+    baseDelay := 500 * time.Millisecond
+    var lastErr error
+    for attempt := 0; attempt < maxRetries; attempt++ {
         lock := &NoteLock{
             NoteID:   uint(id),
             LockedAt: time.Now(),
         }

         result := ds.DB.Where("note_id = ?", id).
             Assign(*lock).
             FirstOrCreate(lock)

         if result.Error != nil {
+            if strings.Contains(strings.ToLower(result.Error.Error()), "database is locked") {
+                delay := baseDelay * time.Duration(attempt+1)
+                time.Sleep(delay)
+                lastErr = result.Error
+                continue
+            }
             return fmt.Errorf("failed to lock note: %w", result.Error)
         }

         return nil
+    }
+    return fmt.Errorf("failed after %d attempts: %w", maxRetries, lastErr)
 }
internal/httpcontroller/handlers/detections.go (1)

Line range hint 653-716: Consider adding transaction for atomic lock operations.

The LockDetection handler should wrap the lock state check and update in a transaction to prevent race conditions.

 func (h *Handlers) LockDetection(c echo.Context) error {
     id := c.QueryParam("id")
     if id == "" {
         h.SSE.SendNotification(Notification{
             Message: "Missing detection ID",
             Type:    "error",
         })
         return h.NewHandlerError(fmt.Errorf("no ID provided"), "Missing detection ID", http.StatusBadRequest)
     }

-    // Get the current note to check its lock status
-    note, err := h.DS.Get(id)
-    if err != nil {
-        h.SSE.SendNotification(Notification{
-            Message: fmt.Sprintf("Failed to get detection: %v", err),
-            Type:    "error",
-        })
-        return h.NewHandlerError(err, "Failed to get detection", http.StatusInternalServerError)
-    }
-
-    // Check current lock status
-    isLocked, err := h.DS.IsNoteLocked(id)
+    var message string
+    err := h.DS.Transaction(func(tx *gorm.DB) error {
+        // Get the current note to check its lock status
+        note, err := h.DS.Get(id)
+        if err != nil {
+            return fmt.Errorf("failed to get detection: %w", err)
+        }
+
+        // Check current lock status
+        isLocked, err := h.DS.IsNoteLocked(id)
+        if err != nil {
+            return fmt.Errorf("failed to check lock status: %w", err)
+        }
+
+        if isLocked {
+            if err := h.DS.UnlockNote(id); err != nil {
+                return fmt.Errorf("failed to unlock detection: %w", err)
+            }
+            message = fmt.Sprintf("Detection of %s unlocked successfully", note.CommonName)
+        } else {
+            if err := h.DS.LockNote(id); err != nil {
+                return fmt.Errorf("failed to lock detection: %w", err)
+            }
+            message = fmt.Sprintf("Detection of %s locked successfully", note.CommonName)
+        }
+        return nil
+    })

     if err != nil {
         h.SSE.SendNotification(Notification{
-            Message: fmt.Sprintf("Failed to check lock status: %v", err),
+            Message: err.Error(),
             Type:    "error",
         })
-        return h.NewHandlerError(err, "Failed to check lock status", http.StatusInternalServerError)
+        return h.NewHandlerError(err, "Failed to process lock operation", http.StatusInternalServerError)
     }

-    var message string
-    if isLocked {
-        // Unlock the detection
-        if err := h.DS.UnlockNote(id); err != nil {
-            h.SSE.SendNotification(Notification{
-                Message: fmt.Sprintf("Failed to unlock detection: %v", err),
-                Type:    "error",
-            })
-            return h.NewHandlerError(err, "Failed to unlock detection", http.StatusInternalServerError)
-        }
-        message = fmt.Sprintf("Detection of %s unlocked successfully", note.CommonName)
-    } else {
-        // Lock the detection
-        if err := h.DS.LockNote(id); err != nil {
-            h.SSE.SendNotification(Notification{
-                Message: fmt.Sprintf("Failed to lock detection: %v", err),
-                Type:    "error",
-            })
-            return h.NewHandlerError(err, "Failed to lock detection", http.StatusInternalServerError)
-        }
-        message = fmt.Sprintf("Detection of %s locked successfully", note.CommonName)
-    }

     // Send success notification
     h.SSE.SendNotification(Notification{
         Message: message,
         Type:    "success",
     })
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bad9b38 and e7d4e21.

📒 Files selected for processing (4)
  • internal/datastore/interfaces.go (10 hunks)
  • internal/datastore/model.go (2 hunks)
  • internal/httpcontroller/handlers/detections.go (1 hunks)
  • views/elements/reviewModal.html (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • views/elements/reviewModal.html
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (10)
internal/datastore/model.go (3)

29-29: LGTM! Well-designed one-to-one relationship.

The Lock field is properly defined with cascade delete behavior and appropriate foreign key constraints.


31-33: LGTM! Clear documentation of virtual fields.

The virtual fields are properly marked with gorm:"-" and have clear comments explaining their population mechanism.


74-80: LGTM! Well-structured lock entity.

The NoteLock struct is well-designed with:

  • Proper primary key
  • Unique index on NoteID to ensure one-to-one relationship
  • Appropriate foreign key constraint with cascade behavior
  • Required timestamp field for audit purposes
internal/datastore/interfaces.go (4)

50-54: LGTM! Well-defined lock management interface.

The lock management methods provide a complete set of operations with clear signatures.


Line range hint 169-183: LGTM! Proper relationship preloading and virtual field population.

The Get method correctly:

  • Preloads the Lock relationship alongside Review and Comments
  • Populates the virtual Locked field based on Lock presence

195-202: LGTM! Proper lock protection.

The Delete method correctly checks for locks before deletion, preventing accidental removal of locked notes.


301-304: LGTM! Proper exclusion of locked notes.

The query correctly excludes locked notes from cleanup using appropriate join and where clauses.

internal/httpcontroller/handlers/detections.go (3)

413-475: LGTM! Well-structured comment processing.

The processComment function:

  • Properly validates comment length
  • Uses transactions effectively
  • Implements retry mechanism for database locks
  • Handles both new comments and updates

477-580: LGTM! Comprehensive review processing.

The processReview function:

  • Properly handles lock state changes
  • Validates review status
  • Prevents marking locked detections as false positive
  • Uses transactions effectively
  • Includes detailed debug logging

582-651: LGTM! Well-organized review handler.

The ReviewDetection handler:

  • Separates concerns into helper functions
  • Provides clear error messages
  • Includes comprehensive debug logging
  • Properly triggers UI refresh

…ogging

- Added retry mechanism for LockNote and UnlockNote methods
- Implemented exponential backoff for database lock contention
- Added unique transaction ID for better logging and traceability
- Updated LockDetection handler to use transactional retry approach
- Improved error handling for database lock scenarios
- Added logging for retry attempts and successful transactions
- Optimized GetNoteLock and IsNoteLocked methods in datastore
- Added explicit lock existence check in UnlockNote method
- Simplified LockDetection handler logic
- Enhanced error handling and logging for lock operations
- Improved transaction retry mechanism for database interactions
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 (4)
internal/httpcontroller/handlers/detections.go (3)

413-475: Improve error handling in processComment function.

The function has good retry logic for database locks, but there are a few areas that could be improved:

  1. The comment length validation should be moved to a constant.
  2. The error messages could be more descriptive.

Apply this diff to improve the error handling:

+const maxCommentLength = 1000

 func (h *Handlers) processComment(noteID uint, comment string, maxRetries int, baseDelay time.Duration) error {
     if comment == "" {
         return nil
     }

     // Validate comment
-    if len(comment) > 1000 {
+    if len(comment) > maxCommentLength {
         h.SSE.SendNotification(Notification{
-            Message: "Comment exceeds maximum length of 1000 characters",
+            Message: fmt.Sprintf("Comment exceeds maximum length of %d characters", maxCommentLength),
             Type:    "error",
         })
-        return fmt.Errorf("comment too long")
+        return fmt.Errorf("comment length %d exceeds maximum of %d characters", len(comment), maxCommentLength)
     }

477-580: Enhance error handling and logging in processReview function.

The function has good retry logic and transaction handling, but could benefit from more detailed error messages and validation.

Apply this diff to improve error handling and logging:

 func (h *Handlers) processReview(noteID uint, verified string, lockDetection bool, maxRetries int, baseDelay time.Duration) error {
     h.Debug("processReview: Starting review process for note ID %d", noteID)
     h.Debug("processReview: Verified status: %s", verified)
     h.Debug("processReview: Lock detection: %v", lockDetection)

     // Validate review status if provided
     if verified != "" && verified != "correct" && verified != "false_positive" {
-        return fmt.Errorf("invalid verification status")
+        return fmt.Errorf("invalid verification status: %s (must be 'correct' or 'false_positive')", verified)
     }

     var lastErr error
     for attempt := 0; attempt < maxRetries; attempt++ {
         err := h.DS.Transaction(func(tx *gorm.DB) error {
             // Get note and existing review in a transaction
             note, err := h.DS.Get(strconv.FormatUint(uint64(noteID), 10))
             if err != nil {
                 h.Debug("processReview: Failed to retrieve note: %v", err)
-                return fmt.Errorf("failed to retrieve note: %w", err)
+                return fmt.Errorf("failed to retrieve note %d: %w", noteID, err)
             }

Line range hint 653-743: Enhance error handling in LockDetection function.

The function has good retry logic but could benefit from more detailed error messages and validation.

Apply this diff to improve error handling:

 func (h *Handlers) LockDetection(c echo.Context) error {
     id := c.QueryParam("id")
     if id == "" {
+        const msg = "Missing detection ID"
         h.SSE.SendNotification(Notification{
-            Message: "Missing detection ID",
+            Message: msg,
             Type:    "error",
         })
-        return h.NewHandlerError(fmt.Errorf("no ID provided"), "Missing detection ID", http.StatusBadRequest)
+        return h.NewHandlerError(fmt.Errorf("no ID provided"), msg, http.StatusBadRequest)
     }

     // Get the current note first to have the species name for messages
     note, err := h.DS.Get(id)
     if err != nil {
+        const msg = "Failed to get detection"
         h.SSE.SendNotification(Notification{
-            Message: fmt.Sprintf("Failed to get detection: %v", err),
+            Message: fmt.Sprintf("%s: %v", msg, err),
             Type:    "error",
         })
-        return h.NewHandlerError(err, "Failed to get detection", http.StatusInternalServerError)
+        return h.NewHandlerError(err, msg, http.StatusInternalServerError)
     }
internal/datastore/interfaces.go (1)

735-868: Enhance error handling and retry logic in lock-related methods.

The lock-related methods have good retry logic but could benefit from more detailed error messages and consistent error handling.

Apply this diff to improve error handling:

 func (ds *DataStore) GetNoteLock(noteID string) (*NoteLock, error) {
     id, err := strconv.ParseUint(noteID, 10, 32)
     if err != nil {
-        return nil, fmt.Errorf("invalid note ID: %w", err)
+        return nil, fmt.Errorf("invalid note ID %q: %w", noteID, err)
     }

     var lock NoteLock
     // Check if the lock exists and get its details in one query
     err = ds.DB.Where("note_id = ?", id).First(&lock).Error
     if err != nil {
         if errors.Is(err, gorm.ErrRecordNotFound) {
             return nil, nil // Return nil if no lock exists
         }
-        return nil, fmt.Errorf("error getting lock details: %w", err)
+        return nil, fmt.Errorf("error getting lock details for note %d: %w", id, err)
     }

     return &lock, nil
 }

 func (ds *DataStore) IsNoteLocked(noteID string) (bool, error) {
     id, err := strconv.ParseUint(noteID, 10, 32)
     if err != nil {
-        return false, fmt.Errorf("invalid note ID: %w", err)
+        return false, fmt.Errorf("invalid note ID %q: %w", noteID, err)
     }

     var count int64
     err = ds.DB.Model(&NoteLock{}).
         Where("note_id = ?", id).
         Count(&count).
         Error

     if err != nil {
-        return false, fmt.Errorf("error checking lock status: %w", err)
+        return false, fmt.Errorf("error checking lock status for note %d: %w", id, err)
     }

     return count > 0, nil
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e7d4e21 and efcd55f.

📒 Files selected for processing (2)
  • internal/datastore/interfaces.go (10 hunks)
  • internal/httpcontroller/handlers/detections.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (5)
internal/httpcontroller/handlers/detections.go (1)

582-651: LGTM! The ReviewDetection function is well-structured.

The function effectively delegates tasks to specialized helper functions and includes comprehensive debug logging.

internal/datastore/interfaces.go (4)

50-54: LGTM! The interface changes are well-defined.

The new lock management methods are clearly named and follow the existing interface pattern.


Line range hint 169-183: LGTM! The Get method changes properly handle lock preloading.

The changes correctly preload the Lock relationship and populate the virtual Locked field.


195-203: LGTM! The Delete method properly checks lock status.

The changes prevent deletion of locked notes, maintaining data integrity.


301-305: LGTM! The GetClipsQualifyingForRemoval method properly excludes locked notes.

The changes ensure that locked notes are not considered for removal during cleanup.

Also applies to: 314-316

@tphakala tphakala merged commit 387412d into main Feb 1, 2025
10 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