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

refactor: update database schema to include review and comment tables #418

Merged
merged 12 commits into from
Jan 31, 2025

Conversation

tphakala
Copy link
Owner

Instead of adding verify and comment columns to notes table this PR adds new tables for review status and comments, this approach avoids slow auto migration process with SQLite with other benefits also

- Created a new Review struct to manage note verification independently
- Removed Verified field from Note model
- Added unique foreign key relationship between Note and Review
- Implemented flexible review status tracking with timestamps
- Implemented methods for retrieving, saving, updating, and deleting note reviews
- Added methods for managing note comments with full CRUD functionality
- Updated query methods to preload reviews and comments with consistent ordering
- Introduced virtual Verified field population for notes
- Moved database management utility functions to a new manage.go file
- Updated auto-migration to include new NoteReview and NoteComment models
…onfiguration

- Implemented comprehensive backup mechanism for SQLite database
- Added disk space and permission checks before creating backups
- Improved database initialization with more robust pragma configuration
- Simplified database connection and close methods
- Enhanced error handling and logging for database operations
- Split Note model's comment and review fields into new NoteReview and NoteComment models
- Added one-to-one and one-to-many relationships for reviews and comments
- Introduced a virtual Verified field to maintain template compatibility
- Updated model structure to improve data organization and relationship management
- Refactored ReviewDetection handler to support separate comment and review updates
- Added support for creating, updating, and retrieving note comments
- Improved handling of species ignore list updates during review
- Updated review modal to display existing comments
- Enhanced error handling and SSE notifications for review and comment operations
Copy link
Contributor

coderabbitai bot commented Jan 31, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This pull request introduces significant enhancements to the datastore package, particularly focusing on note review and comment functionalities. Key modifications include the addition of NoteReview and NoteComment structs, updates to the Note model, and the implementation of various methods for managing these entities. The changes improve data retrieval processes by preloading associated reviews and comments, while also introducing new methods for creating, updating, and deleting comments and reviews across different data access methods.

Changes

File Change Summary
internal/datastore/interfaces.go - Added methods for note reviews and comments
- Updated Get, SpeciesDetections, GetLastDetections, and SearchNotes to preload reviews and comments
- Reintroduced utility functions
internal/datastore/manage.go - Added createGormLogger for GORM logging configuration
- Implemented performAutoMigration for database schema updates
internal/datastore/model.go - Introduced NoteReview and NoteComment structs
- Removed Verified and Comment fields from Note
- Added virtual Verified field and relationships
internal/datastore/sqlite.go - Updated Open method with improved error handling
- Added createBackup method for database backups
- Refined Close method
internal/httpcontroller/handlers/detections.go - Enhanced ReviewDetection method
- Improved comment and verification handling
views/elements/reviewModal.html - Updated comment label and placeholder
- Modified comment display logic

Possibly related PRs

Poem

🐰 Hop, hop, through data's maze,
Comments and reviews now blaze!
Notes get smarter, storage neat,
Our database dance is quite a feat!
Code rabbits rejoice with glee! 🎉


📜 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 1e94f99 and 8fda507.

📒 Files selected for processing (1)
  • internal/datastore/interfaces.go (8 hunks)
✨ 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: 1

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cfc757c and 7248b12.

📒 Files selected for processing (6)
  • internal/datastore/interfaces.go (7 hunks)
  • internal/datastore/manage.go (1 hunks)
  • internal/datastore/model.go (2 hunks)
  • internal/datastore/sqlite.go (2 hunks)
  • internal/httpcontroller/handlers/detections.go (2 hunks)
  • views/elements/reviewModal.html (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (40)
internal/datastore/sqlite.go (19)

5-12: Imports look good
No major issues spotted in the newly added imports.


17-20: Doc comment updated
Stating explicitly that SQLiteStore implements StoreInterface is clearer than referencing DataStore.


29-33: Skipping backup for non-existent database
Returning early when the database file does not exist avoids unnecessary backup operations.


36-39: Robust error handling
Terminating on stat failure ensures we don't attempt backing up a file with invalid metadata.


42-53: Disk space validation
Proactively checking space helps prevent partial backups or silent failures.


56-59: Write permission check
Verifies the backup directory is writable before proceeding, which is good for early error detection.


62-65: Timestamped backup path
Appending a formatted timestamp is a clean approach to organizing multiple backups.


66-71: Source file opening
Clearly handles file-open errors, preventing further steps on invalid file descriptors.


73-79: Creating the backup file
Properly rolls back on creation failure with immediate error returns.


80-82: File copy operation
Using io.Copy is a straightforward method for duplicating the database contents.


85-86: Backup success logging
Logging the newly created backup path aids in both debugging and auditing.


89-98: Creating database directory
Ensuring the directory path exists prevents the open call from failing on missing parent folders.


99-101: Custom GORM logger
Assigning a dedicated logger provides flexibility in log formatting and verbosity.


102-109: SQLite database opening
Handles errors gracefully by returning a wrapped error message about the DB initialization failure.


110-113: *Retrieving sql.DB
Exposes lower-level DB settings and connection properties if needed.


116-129: SQLite PRAGMA configurations
These performance-optimized settings can boost throughput, but consider reliability implications of in-memory journaling.

Are you certain these PRAMA settings provide the desired balance of speed and data integrity for your environment?


131-133: Assigning GORM instance
Preserving the gorm.DB reference for future use within SQLiteStore.


134-136: Auto-migration
Automatically brings the schema in sync with new or modified models.


142-152: Proper database closure
Accounts for the absence of a DB handle and neatly closes the connection if it exists.

internal/datastore/interfaces.go (13)

35-40: New interface methods for note reviews and comments
Helps manage review statuses and user feedback in a more structured way.


163-174: Preloading review & comments in Get()
Unifies retrieval of a Note along with its Review and Comments. Populating Verified is straightforward.


Line range hint 349-370: SpeciesDetections preload
Ensures each detection has its associated Review and Comments for comprehensive data in one query.


380-392: GetLastDetections
Consistent approach to preloading related data. Integrating Verified logic keeps note objects self-contained.


416-430: SearchNotes
Enables searching by common_name or scientific_name, while still fetching Review and Comments.


573-595: GetNoteReview
Returns nil if no review exists, which is a clean way to distinguish unreviewed notes.


596-609: SaveNoteReview
Upsert approach is practical, letting you seamlessly create or update an existing review record.


611-625: GetNoteComments
Returns comments ordered by creation time. Straightforward for retrieving discussion history.


626-633: SaveNoteComment
Standard insertion logic handles new comment creation with minimal overhead.


634-646: DeleteNoteComment
Removes a comment directly by ID, with clear error handling if the ID is invalid.


647-669: UpdateNoteComment
Updates the comment's text and timestamps the edit. A conventional approach.


671-679: getHourRange utility
Neatly calculates start/end times accounting for day wrap-around using modulo arithmetic.


680-686: sortAscendingString utility
Logical helper returning "ASC" or "DESC" for queries based on a boolean.

internal/datastore/manage.go (2)

13-23: GORM logger creation
Sets a warning threshold for slow queries and levels. Keeps logs more readable.


25-38: Auto-migration
Successfully migrates NoteReview and NoteComment along with existing models. Streamlines schema updates.

internal/datastore/model.go (3)

26-31: LGTM! Well-structured relationships and virtual field.

The relationships are properly defined with appropriate GORM annotations. The virtual field maintains backward compatibility with templates.


52-60: LGTM! Well-designed review status model.

The NoteReview struct is well-structured with:

  • Unique index on NoteID ensuring one review per note
  • Appropriate field types and GORM annotations
  • Proper timestamps for tracking review history

62-70: LGTM! Well-designed comment model.

The NoteComment struct is well-structured with:

  • Indexed NoteID for efficient queries
  • Text field for comment content
  • Proper timestamps for tracking comment history
internal/httpcontroller/handlers/detections.go (2)

376-415: LGTM! Well-structured comment handling.

The comment handling logic is well-implemented with:

  • Proper error handling
  • Efficient update/create logic
  • Good use of notifications

417-504: LGTM! Comprehensive review status handling.

The review status handling is well-implemented with:

  • Proper validation of status values
  • Efficient handling of ignore species
  • Good error handling and user notifications
views/elements/reviewModal.html (1)

84-86: LGTM! Well-integrated with new comment model.

The comment field is properly updated to:

  • Use the new Comments array
  • Display the first comment if it exists

internal/httpcontroller/handlers/detections.go Outdated Show resolved Hide resolved
- Simplified performAutoMigration function by removing unnecessary log message
- Kept error handling and migration logic intact
- Reduced verbosity of database initialization process
- Added explicit error handling for detection ID parsing
- Return a clear error message for invalid detection ID format
- Prevent potential runtime errors with proper type conversion check
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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7248b12 and f71df75.

📒 Files selected for processing (2)
  • internal/datastore/manage.go (1 hunks)
  • internal/httpcontroller/handlers/detections.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/datastore/manage.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
internal/httpcontroller/handlers/detections.go (2)

372-374: LGTM! Error handling has been improved.

The error handling for ParseUint has been properly implemented, addressing the previous review comment.


495-508: Skip comment as issues are addressed in previous review comment.

internal/httpcontroller/handlers/detections.go Outdated Show resolved Hide resolved
internal/httpcontroller/handlers/detections.go Outdated Show resolved Hide resolved
- Added comment length validation (max 1000 characters)
- Implemented database transaction for comment update/create
- Enhanced error handling for comment operations
- Prevent race conditions during comment management
- Introduced Transaction method in datastore Interface
- Implemented Transaction method for DataStore struct
- Enables executing database operations within a transaction context
- Supports more robust database interaction patterns
- Updated performAutoMigration function to use a more concise log message
- Removed detailed connection info from log output
- Maintained debug logging with a clearer, more generic message
…ies exclusion

- Extracted species exclusion logic into a separate method
- Implemented database transaction for review processing
- Enhanced error handling and SSE notifications
- Simplified ReviewDetection handler logic
- Added validation for review status and species exclusion
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: 6

🧹 Nitpick comments (2)
internal/datastore/interfaces.go (2)

636-647: Verify comment existence before deletion.

The function should verify if the comment exists before attempting to delete it.

Apply this diff to add existence verification:

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

+    // Verify comment existence
+    var comment NoteComment
+    if err := ds.DB.First(&comment, id).Error; err != nil {
+        if errors.Is(err, gorm.ErrRecordNotFound) {
+            return fmt.Errorf("comment with ID %s not found", commentID)
+        }
+        return fmt.Errorf("error checking comment existence: %w", err)
+    }
+
     if err := ds.DB.Delete(&NoteComment{}, id).Error; err != nil {
         return fmt.Errorf("failed to delete note comment: %w", err)
     }
     return nil
 }

681-687: Simplify the implementation.

The function can be more concise using a ternary-like expression.

Apply this diff to simplify the implementation:

-func sortAscendingString(asc bool) string {
-    if asc {
-        return "ASC"
-    }
-    return "DESC"
-}
+func sortAscendingString(asc bool) string {
+    return map[bool]string{true: "ASC", false: "DESC"}[asc]
+}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f71df75 and 1e94f99.

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

408-410: LGTM! Improved ID parsing and error handling.

The ID parsing now uses strconv.ParseUint with proper error handling, which is more robust than the previous implementation.


413-477: LGTM! Improved comment handling with validation and transaction support.

The comment handling has been significantly improved with:

  1. Length validation to prevent excessively long comments.
  2. Transaction support to prevent race conditions.
  3. Clear error messages for better user experience.

479-524: LGTM! Improved review status handling with validation and transaction support.

The review status handling has been significantly improved with:

  1. Early validation of review status.
  2. Extracted species exclusion logic for better maintainability.
  3. Transaction support to ensure atomicity.
  4. Clear success and error notifications.
internal/datastore/interfaces.go (7)

35-40: LGTM! Well-organized interface updates for note reviews and comments.

The new methods are logically grouped, follow a consistent naming pattern, and provide a comprehensive API for managing note reviews and comments.

Also applies to: 49-49


164-175: LGTM! Efficient preloading of reviews and comments.

The function efficiently preloads reviews and comments with proper ordering, while also populating the virtual Verified field.


Line range hint 350-371: LGTM! Consistent preloading of reviews and comments.

The function maintains consistency with other functions by preloading reviews and comments with proper ordering.


381-393: LGTM! Consistent preloading of reviews and comments.

The function maintains consistency with other functions by preloading reviews and comments with proper ordering.


417-431: LGTM! Consistent preloading of reviews and comments.

The function maintains consistency with other functions by preloading reviews and comments with proper ordering.


575-596: LGTM! Well-implemented review retrieval with proper error handling.

The function properly validates input, handles errors gracefully, and uses a session to control logging behavior.


612-626: LGTM! Well-implemented comment retrieval with proper ordering.

The function properly validates input, orders comments by creation time, and handles errors gracefully.

Comment on lines +364 to +399
// handleSpeciesExclusion handles the logic for managing species in the exclusion list
func (h *Handlers) handleSpeciesExclusion(note *datastore.Note, verified string, ignoreSpecies string) error {
settings := conf.Setting()

if verified == "false_positive" && ignoreSpecies != "" {
// Check if species is already excluded
for _, s := range settings.Realtime.Species.Exclude {
if s == ignoreSpecies {
return nil
}
}

// Add to excluded list
settings.Realtime.Species.Exclude = append(settings.Realtime.Species.Exclude, ignoreSpecies)
if err := conf.SaveSettings(); err != nil {
return fmt.Errorf("failed to save settings: %w", err)
}

h.SSE.SendNotification(Notification{
Message: fmt.Sprintf("%s added to ignore list", ignoreSpecies),
Type: "success",
})
} else if verified == "correct" {
// Check if species is in exclude list
for _, s := range settings.Realtime.Species.Exclude {
if s == note.CommonName {
h.SSE.SendNotification(Notification{
Message: fmt.Sprintf("%s is currently in ignore list. You may want to remove it from Settings.", note.CommonName),
Type: "warning",
})
break
}
}
}
return nil
}
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

Protect against race conditions in settings access.

The function directly accesses and modifies the global settings instance, which could lead to race conditions in a concurrent environment.

Apply this diff to protect against race conditions:

 func (h *Handlers) handleSpeciesExclusion(note *datastore.Note, verified string, ignoreSpecies string) error {
+    // Use mutex to protect settings access
+    h.settingsMutex.Lock()
+    defer h.settingsMutex.Unlock()
+
     settings := conf.Setting()

     if verified == "false_positive" && ignoreSpecies != "" {
         // Check if species is already excluded
         for _, s := range settings.Realtime.Species.Exclude {
             if s == ignoreSpecies {
                 return nil
             }
         }

         // Add to excluded list
         settings.Realtime.Species.Exclude = append(settings.Realtime.Species.Exclude, ignoreSpecies)
         if err := conf.SaveSettings(); err != nil {
             return fmt.Errorf("failed to save settings: %w", err)
         }

         h.SSE.SendNotification(Notification{
             Message: fmt.Sprintf("%s added to ignore list", ignoreSpecies),
             Type:    "success",
         })
     } else if verified == "correct" {
         // Check if species is in exclude list
         for _, s := range settings.Realtime.Species.Exclude {
             if s == note.CommonName {
                 h.SSE.SendNotification(Notification{
                     Message: fmt.Sprintf("%s is currently in ignore list. You may want to remove it from Settings.", note.CommonName),
                     Type:    "warning",
                 })
                 break
             }
         }
     }
     return nil
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 689 to 692
// Transaction executes a function within a transaction.
func (ds *DataStore) Transaction(fc func(tx *gorm.DB) error) error {
return ds.DB.Transaction(fc)
}
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.

The function should validate the input function before attempting to execute it.

Apply this diff to add input validation:

 func (ds *DataStore) Transaction(fc func(tx *gorm.DB) error) error {
+    if fc == nil {
+        return fmt.Errorf("transaction function cannot be nil")
+    }
     return ds.DB.Transaction(fc)
 }
📝 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
// Transaction executes a function within a transaction.
func (ds *DataStore) Transaction(fc func(tx *gorm.DB) error) error {
return ds.DB.Transaction(fc)
}
// Transaction executes a function within a transaction.
func (ds *DataStore) Transaction(fc func(tx *gorm.DB) error) error {
if fc == nil {
return fmt.Errorf("transaction function cannot be nil")
}
return ds.DB.Transaction(fc)
}

Comment on lines 628 to 634
// SaveNoteComment saves a new comment for a note
func (ds *DataStore) SaveNoteComment(comment *NoteComment) error {
if err := ds.DB.Create(comment).Error; err != nil {
return fmt.Errorf("failed to save note comment: %w", err)
}
return nil
}
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.

The function should validate the input comment before attempting to save it.

Apply this diff to add input validation:

 func (ds *DataStore) SaveNoteComment(comment *NoteComment) error {
+    // Validate input
+    if comment == nil {
+        return fmt.Errorf("comment cannot be nil")
+    }
+    if comment.NoteID == 0 {
+        return fmt.Errorf("note ID cannot be zero")
+    }
+    if len(comment.Entry) == 0 {
+        return fmt.Errorf("comment entry cannot be empty")
+    }
+    if len(comment.Entry) > 1000 {
+        return fmt.Errorf("comment entry exceeds maximum length of 1000 characters")
+    }
+
     if err := ds.DB.Create(comment).Error; err != nil {
         return fmt.Errorf("failed to save note comment: %w", err)
     }
     return nil
 }
📝 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
// SaveNoteComment saves a new comment for a note
func (ds *DataStore) SaveNoteComment(comment *NoteComment) error {
if err := ds.DB.Create(comment).Error; err != nil {
return fmt.Errorf("failed to save note comment: %w", err)
}
return nil
}
// SaveNoteComment saves a new comment for a note
func (ds *DataStore) SaveNoteComment(comment *NoteComment) error {
// Validate input
if comment == nil {
return fmt.Errorf("comment cannot be nil")
}
if comment.NoteID == 0 {
return fmt.Errorf("note ID cannot be zero")
}
if len(comment.Entry) == 0 {
return fmt.Errorf("comment entry cannot be empty")
}
if len(comment.Entry) > 1000 {
return fmt.Errorf("comment entry exceeds maximum length of 1000 characters")
}
if err := ds.DB.Create(comment).Error; err != nil {
return fmt.Errorf("failed to save note comment: %w", err)
}
return nil
}

Comment on lines +672 to +679
// getHourRange returns the start and end times for a given hour and duration.
func getHourRange(hour string, duration int) (startTime, endTime string) {
startHour, _ := strconv.Atoi(hour)
endHour := (startHour + duration) % 24
startTime = fmt.Sprintf("%02d:00:00", startHour)
endTime = fmt.Sprintf("%02d:00:00", endHour)
return startTime, endTime
}
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.

The function should validate the hour and duration inputs.

Apply this diff to add input validation:

 func getHourRange(hour string, duration int) (startTime, endTime string) {
+    // Validate hour
+    h, err := strconv.Atoi(hour)
+    if err != nil || h < 0 || h > 23 {
+        return "", ""
+    }
+
+    // Validate duration
+    if duration <= 0 || duration > 24 {
+        return "", ""
+    }
+
-    startHour, _ := strconv.Atoi(hour)
-    endHour := (startHour + duration) % 24
+    endHour := (h + duration) % 24
-    startTime = fmt.Sprintf("%02d:00:00", startHour)
+    startTime = fmt.Sprintf("%02d:00:00", h)
     endTime = fmt.Sprintf("%02d:00:00", endHour)
     return startTime, endTime
 }
📝 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
// getHourRange returns the start and end times for a given hour and duration.
func getHourRange(hour string, duration int) (startTime, endTime string) {
startHour, _ := strconv.Atoi(hour)
endHour := (startHour + duration) % 24
startTime = fmt.Sprintf("%02d:00:00", startHour)
endTime = fmt.Sprintf("%02d:00:00", endHour)
return startTime, endTime
}
// getHourRange returns the start and end times for a given hour and duration.
func getHourRange(hour string, duration int) (startTime, endTime string) {
// Validate hour
h, err := strconv.Atoi(hour)
if err != nil || h < 0 || h > 23 {
return "", ""
}
// Validate duration
if duration <= 0 || duration > 24 {
return "", ""
}
endHour := (h + duration) % 24
startTime = fmt.Sprintf("%02d:00:00", h)
endTime = fmt.Sprintf("%02d:00:00", endHour)
return startTime, endTime
}

Comment on lines +649 to +670
// UpdateNoteComment updates an existing comment's entry
func (ds *DataStore) UpdateNoteComment(commentID string, entry string) error {
id, err := strconv.ParseUint(commentID, 10, 32)
if err != nil {
return fmt.Errorf("invalid comment ID: %w", err)
}

result := ds.DB.Model(&NoteComment{}).Where("id = ?", id).Updates(map[string]interface{}{
"entry": entry,
"updated_at": time.Now(),
})

if result.Error != nil {
return fmt.Errorf("failed to update note comment: %w", result.Error)
}

if result.RowsAffected == 0 {
return fmt.Errorf("comment with ID %s not found", commentID)
}

return nil
}
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 the entry.

The function should validate the entry content before attempting to update it.

Apply this diff to add input validation:

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

+    // Validate entry
+    if len(entry) == 0 {
+        return fmt.Errorf("comment entry cannot be empty")
+    }
+    if len(entry) > 1000 {
+        return fmt.Errorf("comment entry exceeds maximum length of 1000 characters")
+    }
+
     result := ds.DB.Model(&NoteComment{}).Where("id = ?", id).Updates(map[string]interface{}{
         "entry":      entry,
         "updated_at": time.Now(),
     })

     if result.Error != nil {
         return fmt.Errorf("failed to update note comment: %w", result.Error)
     }

     if result.RowsAffected == 0 {
         return fmt.Errorf("comment with ID %s not found", commentID)
     }

     return nil
 }
📝 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
// UpdateNoteComment updates an existing comment's entry
func (ds *DataStore) UpdateNoteComment(commentID string, entry string) error {
id, err := strconv.ParseUint(commentID, 10, 32)
if err != nil {
return fmt.Errorf("invalid comment ID: %w", err)
}
result := ds.DB.Model(&NoteComment{}).Where("id = ?", id).Updates(map[string]interface{}{
"entry": entry,
"updated_at": time.Now(),
})
if result.Error != nil {
return fmt.Errorf("failed to update note comment: %w", result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("comment with ID %s not found", commentID)
}
return nil
}
// UpdateNoteComment updates an existing comment's entry
func (ds *DataStore) UpdateNoteComment(commentID string, entry string) error {
id, err := strconv.ParseUint(commentID, 10, 32)
if err != nil {
return fmt.Errorf("invalid comment ID: %w", err)
}
// Validate entry
if len(entry) == 0 {
return fmt.Errorf("comment entry cannot be empty")
}
if len(entry) > 1000 {
return fmt.Errorf("comment entry exceeds maximum length of 1000 characters")
}
result := ds.DB.Model(&NoteComment{}).Where("id = ?", id).Updates(map[string]interface{}{
"entry": entry,
"updated_at": time.Now(),
})
if result.Error != nil {
return fmt.Errorf("failed to update note comment: %w", result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("comment with ID %s not found", commentID)
}
return nil
}

Comment on lines +598 to +610
// SaveNoteReview saves or updates a note review
func (ds *DataStore) SaveNoteReview(review *NoteReview) error {
// Use upsert operation to either create or update the review
result := ds.DB.Where("note_id = ?", review.NoteID).
Assign(*review).
FirstOrCreate(review)

if result.Error != nil {
return fmt.Errorf("failed to save note review: %w", result.Error)
}

return nil
}
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.

The function should validate the input review before attempting to save it.

Apply this diff to add input validation:

 func (ds *DataStore) SaveNoteReview(review *NoteReview) error {
+    // Validate input
+    if review == nil {
+        return fmt.Errorf("review cannot be nil")
+    }
+    if review.NoteID == 0 {
+        return fmt.Errorf("note ID cannot be zero")
+    }
+    if review.Verified != "correct" && review.Verified != "false_positive" {
+        return fmt.Errorf("invalid verification status: %s", review.Verified)
+    }
+
     // Use upsert operation to either create or update the review
     result := ds.DB.Where("note_id = ?", review.NoteID).
         Assign(*review).
         FirstOrCreate(review)

     if result.Error != nil {
         return fmt.Errorf("failed to save note review: %w", result.Error)
     }

     return nil
 }
📝 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
// SaveNoteReview saves or updates a note review
func (ds *DataStore) SaveNoteReview(review *NoteReview) error {
// Use upsert operation to either create or update the review
result := ds.DB.Where("note_id = ?", review.NoteID).
Assign(*review).
FirstOrCreate(review)
if result.Error != nil {
return fmt.Errorf("failed to save note review: %w", result.Error)
}
return nil
}
// SaveNoteReview saves or updates a note review
func (ds *DataStore) SaveNoteReview(review *NoteReview) error {
// Validate input
if review == nil {
return fmt.Errorf("review cannot be nil")
}
if review.NoteID == 0 {
return fmt.Errorf("note ID cannot be zero")
}
if review.Verified != "correct" && review.Verified != "false_positive" {
return fmt.Errorf("invalid verification status: %s", review.Verified)
}
// Use upsert operation to either create or update the review
result := ds.DB.Where("note_id = ?", review.NoteID).
Assign(*review).
FirstOrCreate(review)
if result.Error != nil {
return fmt.Errorf("failed to save note review: %w", result.Error)
}
return nil
}

- Added nil and input validation for SaveNoteComment method
- Implemented length check for comment entries (max 1000 characters)
- Added nil check for transaction function to prevent runtime errors
- Improved error handling for comment and transaction operations
@tphakala tphakala merged commit 8f40379 into main Jan 31, 2025
9 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