Skip to content

feat: Add DB fetchers to support OpenGraph Analysis: BED-7417#2383

Merged
brandonshearin merged 13 commits intomainfrom
BED-7417
Feb 25, 2026
Merged

feat: Add DB fetchers to support OpenGraph Analysis: BED-7417#2383
brandonshearin merged 13 commits intomainfrom
BED-7417

Conversation

@brandonshearin
Copy link
Copy Markdown
Contributor

@brandonshearin brandonshearin commented Feb 19, 2026

Description

These changes added various database getters for types that the analysis pipeline needs.

Motivation and Context

BED-7417
BHE analysis pipeline needs to pull in data about extensions registered with OpenGraph. This PR contains a small subset of the huge changeset that was recently PR'd.

How Has This Been Tested?

Screenshots (optional):

Types of changes

  • Chore (a change that does not modify the application functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Database Migrations

Checklist:

Summary by CodeRabbit

  • New Features

    • Retrieve traversable relationship kinds and environments scoped to a specific extension.
    • Batch retrieval APIs for kinds, source kinds, and related lookups (variadic IDs).
  • Refactor

    • Database lookups moved to bulk queries with deduplication and preserved ordering.
    • Added a generic dedupe utility to support ID deduplication.
  • Tests

    • Integration tests updated and expanded to cover batch retrievals and extension-scoped behavior.
  • Chores

    • Mocks updated to support new batch and extension-scoped methods.

@brandonshearin brandonshearin changed the title Add DB fetchers to support OpenGraph Analysis: BED-7417 feat: Add DB fetchers to support OpenGraph Analysis: BED-7417 Feb 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds variadic bulk retrievals for kinds and source kinds; implements two graph-schema DB methods to fetch traversable relationship kinds and environments by schema extension ID; updates mocks and integration tests to use the new slice-returning APIs.

Changes

Cohort / File(s) Summary
Graph Schema Methods
cmd/api/src/database/graphschema.go
Added OpenGraphSchema methods and BloodhoundDB implementations: GetTraversableRelationshipKindsByExtensionID(ctx context.Context, extensionID int32) and GetEnvironmentsByExtensionId(ctx context.Context, extensionId int32) with SQL queries scoped by schema_extension_id and is_traversable.
Kind API & Tests
cmd/api/src/database/kind.go, cmd/api/src/database/kind_integration_test.go, cmd/api/src/database/upsert_schema_extension_integration_test.go
Replaced single-ID GetKindById with variadic GetKindsByIDs(...int32); deduplicates IDs, returns slices, enforces returned-count == requested-count; updated tests and call sites to handle slice responses and adjust assertions.
SourceKinds API & Tests
cmd/api/src/database/sourcekinds.go, cmd/api/src/database/sourcekinds_integration_test.go
Added GetSourceKindsByIDs(ctx context.Context, ids ...int32) to interface and BloodhoundDB with dedupe, empty-input handling, active filter, ORDER BY id, and returned-count validation; added integration tests covering empty/unknown/single/multiple IDs.
Database Mocks
cmd/api/src/database/mocks/db.go
Extended mocks and recorder with new methods/recorders: GetEnvironmentsByExtensionId, GetKindsByIDs, GetSourceKindsByIDs, GetTraversableRelationshipKindsByExtensionID; renamed/adjusted prior kind-related mock signatures (e.g., replaced GetKindById usage).
Utils
cmd/api/src/utils/dedupe.go
Added generic Dedupe[T comparable](entries []T) []T utility used for ID deduplication.

Sequence Diagram(s)

(Skipped — changes are DB accessors, mocks, and tests without a multi-component sequential flow requiring visualization.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • LawsonWillard
  • kpowderly
  • mistahj67
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides motivation (BED-7417), context (analysis pipeline needs), and lists change types, but the 'How Has This Been Tested?' section is empty, lacking test details required by template. Complete the 'How Has This Been Tested?' section with specific details about testing environment, test cases run, and verification that existing tests still pass.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of the PR: adding database fetchers to support OpenGraph analysis for the specified Jira ticket.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BED-7417

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

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

@coderabbitai coderabbitai Bot added api A pull request containing changes affecting the API code. enhancement New feature or request labels Feb 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
cmd/api/src/database/kind.go (1)

52-66: Hoist kinds into a top‑level var block

This aligns with the repository’s variable‑hoisting guideline.

♻️ Suggested refactor
 func (s *BloodhoundDB) GetKindsByIDs(ctx context.Context, ids ...int32) ([]model.Kind, error) {
+	var (
+		kinds []model.Kind
+	)
 	if len(ids) == 0 {
 		return []model.Kind{}, nil
 	}
 
 	const query = `
 		SELECT id, name
 		FROM kind
 		WHERE id = ANY(?)
 		ORDER BY id;
 	`
 
-	var kinds []model.Kind
 	result := s.db.WithContext(ctx).Raw(query, pq.Array(ids)).Scan(&kinds)
As per coding guidelines: "Group variable initializations in a `var ( ... )` block and hoist them to the top of the function when possible".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/kind.go` around lines 52 - 66, The local slice
declaration "var kinds []model.Kind" inside GetKindsByIDs should be hoisted into
a top-level var block at the start of the function per repo guidelines; move the
declaration into a var (...) block immediately after the nil-check (or function
entry) so the function begins with "var ( kinds []model.Kind )" and leave the
later Scan call unchanged (Scan(&kinds)) so behavior is identical.
cmd/api/src/database/graphschema.go (1)

564-577: Hoist kinds into a var block

Small style fix to comply with repo guidelines.

♻️ Suggested refactor
 	const query = `
 		SELECT rk.id, k.name, rk.schema_extension_id, rk.description, rk.is_traversable,
 		       rk.created_at, rk.updated_at, rk.deleted_at
 		FROM schema_relationship_kinds rk
 		JOIN kind k ON rk.kind_id = k.id
 		WHERE rk.schema_extension_id = $1 AND rk.is_traversable = true
 	`
 
-	var kinds model.GraphSchemaRelationshipKinds
+	var (
+		kinds model.GraphSchemaRelationshipKinds
+	)
As per coding guidelines: "Group variable initializations in a `var ( ... )` block and hoist them to the top of the function when possible".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/graphschema.go` around lines 564 - 577, The local
variable declaration "var kinds model.GraphSchemaRelationshipKinds" should be
hoisted into a grouped var block at the top of
GetTraversableRelationshipKindsByExtensionID to follow repo guidelines; update
the function GetTraversableRelationshipKindsByExtensionID to declare kinds (and
any other local vars you want grouped) inside a var (...) block near the start
of the function before using s.db.WithContext(...).Raw(...).Scan(&kinds).
cmd/api/src/database/sourcekinds.go (1)

185-208: Use a top‑level var block for rawKinds / sourceKinds

This keeps variable declarations grouped and hoisted per repo guidelines.

♻️ Suggested refactor
 func (s *BloodhoundDB) GetSourceKindsByIDs(ctx context.Context, ids ...int32) ([]SourceKind, error) {
+	var (
+		rawKinds   []rawSourceKind
+		sourceKinds []SourceKind
+	)
 	if len(ids) == 0 {
 		return []SourceKind{}, nil
 	}
@@
-	var rawKinds []rawSourceKind
 	result := s.db.WithContext(ctx).Raw(query, pq.Array(ids)).Scan(&rawKinds)
@@
-	sourceKinds := make([]SourceKind, len(rawKinds))
+	sourceKinds = make([]SourceKind, len(rawKinds))
As per coding guidelines: "Group variable initializations in a `var ( ... )` block and hoist them to the top of the function when possible".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/sourcekinds.go` around lines 185 - 208, Declare rawKinds
and sourceKinds in a top-level var block at the start of the function (alongside
rawSourceKind type) instead of inline: hoist "var rawKinds []rawSourceKind" and
"var sourceKinds []SourceKind" into a single var (...) block before using
s.db.Raw(...). Keep the rawSourceKind type where it is or move it above the var
block if needed so the declarations compile; then remove the inline :=
declarations and allocate sourceKinds with make after you know len(rawKinds) (or
set its length in the var with nil/slice literal if preferred).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/api/src/database/sourcekinds.go`:
- Around line 177-199: GetSourceKindsByIDs currently returns inactive kinds
because the SQL in the query (used in GetSourceKindsByIDs) lacks the same active
filter as GetSourceKindByName/GetSourceKindByID/GetSourceKinds; update the const
query used to populate rawSourceKind to include "AND sk.active = true" (i.e.,
WHERE sk.id = ANY(?) AND sk.active = true) so only active source kinds are
returned, keeping the rest of the logic (rawSourceKind struct, Scan into
rawKinds, and the not-found check) unchanged.

---

Nitpick comments:
In `@cmd/api/src/database/graphschema.go`:
- Around line 564-577: The local variable declaration "var kinds
model.GraphSchemaRelationshipKinds" should be hoisted into a grouped var block
at the top of GetTraversableRelationshipKindsByExtensionID to follow repo
guidelines; update the function GetTraversableRelationshipKindsByExtensionID to
declare kinds (and any other local vars you want grouped) inside a var (...)
block near the start of the function before using
s.db.WithContext(...).Raw(...).Scan(&kinds).

In `@cmd/api/src/database/kind.go`:
- Around line 52-66: The local slice declaration "var kinds []model.Kind" inside
GetKindsByIDs should be hoisted into a top-level var block at the start of the
function per repo guidelines; move the declaration into a var (...) block
immediately after the nil-check (or function entry) so the function begins with
"var ( kinds []model.Kind )" and leave the later Scan call unchanged
(Scan(&kinds)) so behavior is identical.

In `@cmd/api/src/database/sourcekinds.go`:
- Around line 185-208: Declare rawKinds and sourceKinds in a top-level var block
at the start of the function (alongside rawSourceKind type) instead of inline:
hoist "var rawKinds []rawSourceKind" and "var sourceKinds []SourceKind" into a
single var (...) block before using s.db.Raw(...). Keep the rawSourceKind type
where it is or move it above the var block if needed so the declarations
compile; then remove the inline := declarations and allocate sourceKinds with
make after you know len(rawKinds) (or set its length in the var with nil/slice
literal if preferred).

Comment thread cmd/api/src/database/sourcekinds.go Outdated
Comment thread cmd/api/src/database/sourcekinds.go Outdated
@brandonshearin brandonshearin self-assigned this Feb 23, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmd/api/src/database/sourcekinds.go (1)

177-183: query should be const for consistency with the rest of the file.

Every other function in this file declares its SQL string as const query = \...`. Using query :=` makes it a mutable local variable unnecessarily.

♻️ Proposed fix
-	query := `
+	const query = `
 		SELECT sk.id, k.name, sk.active
 		FROM source_kinds sk
 		JOIN kind k ON k.id = sk.kind_id
 		WHERE sk.id IN (?) AND sk.active = true
 		ORDER BY sk.id;
 	`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/sourcekinds.go` around lines 177 - 183, The SQL string
`query` in the block (currently declared with `query :=`) should be made a
constant like the rest of the file: change the mutable local declaration to an
immutable `const query = ...` so the SQL is consistent across functions (look
for the `query` variable in the function surrounding the SELECT joining
`source_kinds sk` and `kind k` and replace `:=` with a `const` declaration).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/api/src/database/sourcekinds.go`:
- Around line 197-199: The length check compares rawKinds to the original
variadic ids, which can contain duplicates and cause a false ErrNotFound;
deduplicate the ids slice before using it in the SQL IN and before the
len(rawKinds) != len(...) check (e.g., build a uniqueIDs set from ids, use
uniqueIDs in the query and compare len(rawKinds) to len(uniqueIDs)), and keep
references to ErrNotFound, rawKinds, ids and the sk.id IN usage to locate where
to change.

---

Nitpick comments:
In `@cmd/api/src/database/sourcekinds.go`:
- Around line 177-183: The SQL string `query` in the block (currently declared
with `query :=`) should be made a constant like the rest of the file: change the
mutable local declaration to an immutable `const query = ...` so the SQL is
consistent across functions (look for the `query` variable in the function
surrounding the SELECT joining `source_kinds sk` and `kind k` and replace `:=`
with a `const` declaration).

ℹ️ Review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ccd76f and a08cc07.

📒 Files selected for processing (3)
  • cmd/api/src/database/mocks/db.go
  • cmd/api/src/database/sourcekinds.go
  • cmd/api/src/database/sourcekinds_integration_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/api/src/database/sourcekinds_integration_test.go

Comment thread cmd/api/src/database/sourcekinds.go Outdated
Comment thread cmd/api/src/database/sourcekinds_integration_test.go
Comment thread cmd/api/src/database/sourcekinds.go
Comment thread cmd/api/src/database/kind_integration_test.go Outdated
Copy link
Copy Markdown
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.

🧹 Nitpick comments (2)
cmd/api/src/database/kind_integration_test.go (2)

134-137: Assert exact ID equality, not just positivity.

On Line 136, asserting > 0 is weaker than needed; this test should prove the returned row matches the requested ID.

Proposed assertion tightening
-				assert.Greater(t, kinds[0].ID, int32(0))
+				assert.Equal(t, createdKind.ID, kinds[0].ID)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/kind_integration_test.go` around lines 134 - 137, The
test currently asserts only that the returned kind ID is positive using
assert.Greater on kinds[0].ID; replace that weak check with an exact equality
assertion comparing the returned ID to the expected ID (tt.want.kind.ID) so
change the assertion involving assert.Greater(kinds[0].ID, int32(0)) to
assert.Equal(t, tt.want.kind.ID, kinds[0].ID) to ensure the returned row matches
the requested ID.

94-99: Use a guaranteed-missing ID for the not-found case.

Line 98 hard-codes 2141, which may eventually exist as fixtures/migrations grow. Using a negative ID keeps this test deterministic.

Proposed deterministic fixture ID
-				return model.Kind{
-					ID: 2141,
-				}
+				return model.Kind{
+					ID: -1,
+				}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/kind_integration_test.go` around lines 94 - 99, The test
case "fail - unknown kind" uses a hard-coded ID 2141 that could collide with
future fixtures; change the returned model.Kind.ID in the setup closure to a
guaranteed-missing value (e.g., -1) so the not-found case remains
deterministic—locate the setup func in kind_integration_test.go that constructs
model.Kind and replace ID: 2141 with ID: -1.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmd/api/src/database/kind_integration_test.go`:
- Around line 134-137: The test currently asserts only that the returned kind ID
is positive using assert.Greater on kinds[0].ID; replace that weak check with an
exact equality assertion comparing the returned ID to the expected ID
(tt.want.kind.ID) so change the assertion involving assert.Greater(kinds[0].ID,
int32(0)) to assert.Equal(t, tt.want.kind.ID, kinds[0].ID) to ensure the
returned row matches the requested ID.
- Around line 94-99: The test case "fail - unknown kind" uses a hard-coded ID
2141 that could collide with future fixtures; change the returned model.Kind.ID
in the setup closure to a guaranteed-missing value (e.g., -1) so the not-found
case remains deterministic—locate the setup func in kind_integration_test.go
that constructs model.Kind and replace ID: 2141 with ID: -1.

ℹ️ Review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a08cc07 and cc8a6f7.

📒 Files selected for processing (2)
  • cmd/api/src/database/kind_integration_test.go
  • cmd/api/src/database/sourcekinds_integration_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/api/src/database/sourcekinds_integration_test.go

Copy link
Copy Markdown
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.

🧹 Nitpick comments (3)
cmd/api/src/database/kind.go (2)

61-66: Inconsistent SQL placeholder style within the same file.

GetKindByName (line 35) uses $1 (PostgreSQL-native), while GetKindsByIDs uses ? (GORM-interpolated). Both work, but mixing styles within the same file hurts readability and grep-ability.

Consider aligning to $1 for consistency:

Suggested diff
 	const query = `
 		SELECT id, name
 		FROM kind
-		WHERE id = ANY(?)
+		WHERE id = ANY($1)
 		ORDER BY id;
 	`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/kind.go` around lines 61 - 66, The SQL placeholder style
is inconsistent: GetKindByName uses PostgreSQL-style $1 while GetKindsByIDs uses
a GORM-style ?, so update the query constant in GetKindsByIDs (the const query
used by GetKindsByIDs) to use $1 instead of ? (i.e., WHERE id = ANY($1) ORDER BY
id), and adjust the call site in GetKindsByIDs to pass the IDs as the single
parameter in the form expected by the PostgreSQL driver (e.g., a slice/pg array
or the driver-specific array wrapper) so parameter binding still works.

75-77: Consider a more informative not-found error.

When some IDs are missing, the caller receives a bare ErrNotFound with no indication of which IDs weren't found. For a debugging-friendly approach, you could compute the diff and include the missing IDs in the wrapped error, similar to the pattern on line 72.

Example
if len(kinds) != len(uniqueIDs) {
    foundIDs := make(map[int32]struct{}, len(kinds))
    for _, k := range kinds {
        foundIDs[k.ID] = struct{}{}
    }
    var missing []int32
    for _, id := range uniqueIDs {
        if _, ok := foundIDs[id]; !ok {
            missing = append(missing, id)
        }
    }
    return nil, fmt.Errorf("kinds not found for IDs %v: %w", missing, ErrNotFound)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/kind.go` around lines 75 - 77, When len(kinds) !=
len(uniqueIDs) in the function handling lookups, compute which IDs are missing
and return a wrapped error that lists them instead of the bare ErrNotFound;
e.g., build a map from kinds (use k.ID) to detect missing IDs from uniqueIDs,
collect missing []int32, and return fmt.Errorf("kinds not found for IDs %v: %w",
missing, ErrNotFound). Ensure fmt is imported if not already and follow the same
wrapping pattern used at the nearby line 72.
cmd/api/src/database/sourcekinds.go (1)

172-215: Group local initializations into var (...) blocks for guideline consistency.

In both GetSourceKindsByIDs and dedupeInt32s, locals are initialized separately where a grouped var block is feasible.

♻️ Proposed refactor
 func (s *BloodhoundDB) GetSourceKindsByIDs(ctx context.Context, ids ...int32) ([]SourceKind, error) {
 	if len(ids) == 0 {
 		return []SourceKind{}, nil
 	}

-	// Dedupe IDs so the length check against query results doesn't produce a
-	// false-positive ErrNotFound when callers pass duplicate values.
-	uniqueIDs := dedupeInt32s(ids)
-
 	query := `
 		SELECT sk.id, k.name, sk.active
 		FROM source_kinds sk
 		JOIN kind k ON k.id = sk.kind_id
 		WHERE sk.id IN (?) AND sk.active = true
 		ORDER BY sk.id;
 	`

 	type rawSourceKind struct {
 		ID     int
 		Name   string
 		Active bool
 	}

-	var rawKinds []rawSourceKind
+	var (
+		// Dedupe IDs so the length check against query results doesn't produce a
+		// false-positive ErrNotFound when callers pass duplicate values.
+		uniqueIDs = dedupeInt32s(ids)
+		rawKinds  []rawSourceKind
+	)
 	result := s.db.WithContext(ctx).Raw(query, uniqueIDs).Scan(&rawKinds)
 	if err := result.Error; err != nil {
 		return nil, fmt.Errorf("failed to fetch source kinds by IDs: %w", err)
 	}
@@
 func dedupeInt32s(ids []int32) []int32 {
-	seen := make(map[int32]struct{}, len(ids))
-	unique := make([]int32, 0, len(ids))
+	var (
+		seen   = make(map[int32]struct{}, len(ids))
+		unique = make([]int32, 0, len(ids))
+	)
 	for _, id := range ids {
 		if _, ok := seen[id]; !ok {
 			seen[id] = struct{}{}
 			unique = append(unique, id)
 		}

As per coding guidelines: "Group variable initializations in a var ( ... ) block and hoist them to the top of the function when possible".

Also applies to: 243-253

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

In `@cmd/api/src/database/sourcekinds.go` around lines 172 - 215, Group local
variable declarations in GetSourceKindsByIDs and dedupeInt32s into var (...)
blocks hoisted near the top of each function: move declarations like uniqueIDs,
query, the rawSourceKind type (if allowed), rawKinds, result and sourceKinds
into a single var (...) block in GetSourceKindsByIDs and similarly group locals
in dedupeInt32s; keep initialization expressions as needed but ensure the
grouped var block follows the initial early-return (len(ids) check) and
maintains the same types and values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmd/api/src/database/kind.go`:
- Around line 61-66: The SQL placeholder style is inconsistent: GetKindByName
uses PostgreSQL-style $1 while GetKindsByIDs uses a GORM-style ?, so update the
query constant in GetKindsByIDs (the const query used by GetKindsByIDs) to use
$1 instead of ? (i.e., WHERE id = ANY($1) ORDER BY id), and adjust the call site
in GetKindsByIDs to pass the IDs as the single parameter in the form expected by
the PostgreSQL driver (e.g., a slice/pg array or the driver-specific array
wrapper) so parameter binding still works.
- Around line 75-77: When len(kinds) != len(uniqueIDs) in the function handling
lookups, compute which IDs are missing and return a wrapped error that lists
them instead of the bare ErrNotFound; e.g., build a map from kinds (use k.ID) to
detect missing IDs from uniqueIDs, collect missing []int32, and return
fmt.Errorf("kinds not found for IDs %v: %w", missing, ErrNotFound). Ensure fmt
is imported if not already and follow the same wrapping pattern used at the
nearby line 72.

In `@cmd/api/src/database/sourcekinds.go`:
- Around line 172-215: Group local variable declarations in GetSourceKindsByIDs
and dedupeInt32s into var (...) blocks hoisted near the top of each function:
move declarations like uniqueIDs, query, the rawSourceKind type (if allowed),
rawKinds, result and sourceKinds into a single var (...) block in
GetSourceKindsByIDs and similarly group locals in dedupeInt32s; keep
initialization expressions as needed but ensure the grouped var block follows
the initial early-return (len(ids) check) and maintains the same types and
values.

ℹ️ Review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc8a6f7 and 2527a51.

📒 Files selected for processing (2)
  • cmd/api/src/database/kind.go
  • cmd/api/src/database/sourcekinds.go

Comment thread cmd/api/src/database/sourcekinds.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
cmd/api/src/database/kind.go (2)

61-66: Minor inconsistency: query could be declared as const.

GetKindByName declares its query string as const (line 32), but here it uses :=. Since the query is a string literal that doesn't change, const would be consistent and signal immutability.

Proposed fix
-	query := `
+	const query = `
 		SELECT id, name
 		FROM kind
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/kind.go` around lines 61 - 66, The SQL query string in
kind.go (variable named query inside the function that selects "SELECT id, name
FROM kind WHERE id IN (?) ORDER BY id;") should be declared as a const instead
of a short variable (:=) to match the immutability used by GetKindByName; change
the declaration from query := `...` to const query = `...` within the same
function (e.g., GetKindByIDs or wherever that query appears) so the literal is
clearly immutable and consistent across the file.

75-77: ErrNotFound is returned without wrapping — consider adding context.

Line 72 wraps query errors with fmt.Errorf("failed to fetch kinds by IDs: %w", err), but the not-found case on line 76 returns a bare ErrNotFound. For consistency and debuggability, consider wrapping it similarly so callers can distinguish the source when multiple lookups use ErrNotFound.

Proposed fix
 	if len(kinds) != len(uniqueIDs) {
-		return nil, ErrNotFound
+		return nil, fmt.Errorf("one or more kinds not found: %w", ErrNotFound)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/api/src/database/kind.go` around lines 75 - 77, The function compares
len(kinds) to len(uniqueIDs) and returns the bare ErrNotFound; change this to
wrap ErrNotFound with context so callers can tell where it came from (e.g., use
fmt.Errorf to include a message like "failed to fetch kinds by IDs" or include
the uniqueIDs) — update the return to something like fmt.Errorf("failed to fetch
kinds for IDs %v: %w", uniqueIDs, ErrNotFound) so ErrNotFound is preserved but
annotated; ensure you import fmt if not already.
cmd/api/src/utils/dedupe.go (1)

19-29: Named return violates coding guidelines.

The function uses a named return unique. Per the Go coding guidelines for this repo, named returns are not allowed — all return variables must be defined inside the function body.

Proposed fix
-func Dedupe[T comparable](entries []T) (unique []T) {
+func Dedupe[T comparable](entries []T) []T {
+	var unique []T
 	seen := make(map[T]struct{}, len(entries))
 	for _, e := range entries {
 		if _, ok := seen[e]; !ok {

As per coding guidelines: "Named returns are not allowed; all return variables must be defined in the function".

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

In `@cmd/api/src/utils/dedupe.go` around lines 19 - 29, The Dedupe function
currently uses a named return value `unique`; change it to use an explicitly
declared local variable instead. In function Dedupe[T comparable](entries []T)
declare a local slice variable (e.g., var unique []T or unique := make([]T, 0,
len(entries))) inside the body, populate it as before, and return it with a bare
return of that variable (return unique) rather than relying on a named result.
Ensure the function signature no longer names the return.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/api/src/database/kind.go`:
- Around line 61-69: The SQL query uses "WHERE id IN (?)" which prevents GORM v2
from expanding the slice correctly; update the query string in kind.go (the
variable query used by s.db.WithContext(ctx).Raw(query, uniqueIDs).Scan(&kinds))
to use "WHERE id IN ?" (remove the parentheses) so GORM can expand uniqueIDs as
a slice for the IN clause; keep the rest of the call site (Raw and Scan into
kinds) unchanged.

---

Nitpick comments:
In `@cmd/api/src/database/kind.go`:
- Around line 61-66: The SQL query string in kind.go (variable named query
inside the function that selects "SELECT id, name FROM kind WHERE id IN (?)
ORDER BY id;") should be declared as a const instead of a short variable (:=) to
match the immutability used by GetKindByName; change the declaration from query
:= `...` to const query = `...` within the same function (e.g., GetKindByIDs or
wherever that query appears) so the literal is clearly immutable and consistent
across the file.
- Around line 75-77: The function compares len(kinds) to len(uniqueIDs) and
returns the bare ErrNotFound; change this to wrap ErrNotFound with context so
callers can tell where it came from (e.g., use fmt.Errorf to include a message
like "failed to fetch kinds by IDs" or include the uniqueIDs) — update the
return to something like fmt.Errorf("failed to fetch kinds for IDs %v: %w",
uniqueIDs, ErrNotFound) so ErrNotFound is preserved but annotated; ensure you
import fmt if not already.

In `@cmd/api/src/utils/dedupe.go`:
- Around line 19-29: The Dedupe function currently uses a named return value
`unique`; change it to use an explicitly declared local variable instead. In
function Dedupe[T comparable](entries []T) declare a local slice variable (e.g.,
var unique []T or unique := make([]T, 0, len(entries))) inside the body,
populate it as before, and return it with a bare return of that variable (return
unique) rather than relying on a named result. Ensure the function signature no
longer names the return.

ℹ️ Review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2527a51 and e7eb88d.

📒 Files selected for processing (3)
  • cmd/api/src/database/kind.go
  • cmd/api/src/database/sourcekinds.go
  • cmd/api/src/utils/dedupe.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/api/src/database/sourcekinds.go

Comment thread cmd/api/src/database/kind.go
@brandonshearin brandonshearin merged commit 52d97de into main Feb 25, 2026
13 checks passed
@brandonshearin brandonshearin deleted the BED-7417 branch February 25, 2026 20:14
@github-actions github-actions Bot locked and limited conversation to collaborators Feb 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

api A pull request containing changes affecting the API code. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants