Skip to content

Conversation

@jsbroks
Copy link
Member

@jsbroks jsbroks commented May 6, 2025

Summary by CodeRabbit

  • New Features

    • Added support for updating resource relationship rules via a new PATCH endpoint.
    • Introduced new metadata matching capabilities with key-value pairs for resource relationship rules.
    • Enhanced API schemas to support richer metadata criteria for resource relationships.
  • Bug Fixes

    • Improved error handling and response codes for resource relationship rule creation, including conflict and server error responses.
  • Documentation

    • Updated OpenAPI documentation to reflect new endpoints, schemas, and error responses.
  • Tests

    • Expanded test coverage for resource relationship rules, including creation and update scenarios with metadata.
  • Chores

    • Added new permissions for managing resource relationship rules.
    • Extended RBAC and database schema to support new resource relationship rule features.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 6, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This update introduces support for richer metadata-based matching in resource relationship rules, enabling both key and key-value constraints. It adds a PATCH endpoint for updating rules, refines the POST endpoint to be create-only with conflict detection, updates schemas and OpenAPI specs, expands RBAC and permission enums, and includes corresponding migrations and tests.

Changes

Files / Paths Change Summary
apps/webservice/src/app/api/v1/jobs/[jobId]/get-job.ts Refactored import of getResourceParents to use package import path.
apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/openapi.ts
openapi.v1.json
e2e/api/schema.ts
Added PATCH endpoint OpenAPI spec for updating resource relationship rules; introduced UpdateResourceRelationshipRule schema; updated response codes and error handling.
apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/route.ts Added PATCH handler for updating a resource relationship rule, including validation, authorization, transactional update logic, and error handling.
apps/webservice/src/app/api/v1/resource-relationship-rules/openapi.ts
e2e/api/schema.ts
openapi.v1.json
Updated POST endpoint OpenAPI spec: operationId renamed, error responses clarified, and schemas extended for metadata matching.
apps/webservice/src/app/api/v1/resource-relationship-rules/route.ts Changed POST handler to create-only (no upsert), added conflict detection, updated validation and permissions, improved error handling, and simplified metadata insertion logic.
e2e/tests/api/resource-relationships.spec.ts Enhanced tests: added cases for metadata match/equal, switched update to PATCH, improved cleanup, and removed console logs.
packages/api/src/router/resource-relationship-rules.ts Modified query to eagerly load both metadataMatches and new metadataEquals relations.
packages/auth/src/utils/rbac.ts Added support for resourceRelationshipRule scope in RBAC, with a handler to resolve scopes including workspace.
packages/db/drizzle/0099_pink_chamber.sql
packages/db/drizzle/meta/_journal.json
Migration: added resourceRelationshipRule to scope_type enum; created new table for metadata equals; journal entry added.
packages/db/src/queries/get-resource-parents.ts Refactored query logic to support both metadata key and key-value matching using new existence checks and updated schema imports.
packages/db/src/schema/rbac.ts Added "resourceRelationshipRule" value to scopeType enum.
packages/db/src/schema/resource-relationship-rule.ts Added new table and relations for metadata equals; extended schemas for creation and update to support metadata keys; introduced partial update schema.
packages/validators/src/auth/index.ts Added new permissions: ResourceRelationshipRuleCreate, ResourceRelationshipRuleUpdate, and ResourceRelationshipRuleDelete.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant Auth
    participant DB

    Client->>API: PATCH /v1/resource-relationship-rules/{ruleId}
    API->>Auth: Authenticate & Authorize (ResourceRelationshipRuleUpdate)
    Auth-->>API: Success/Failure
    API->>DB: Check if rule exists (by ruleId)
    DB-->>API: Rule found/not found
    alt Rule exists
        API->>DB: Transaction: Update rule, replace metadataMatches and metadataEquals
        DB-->>API: Updated rule & metadata
        API-->>Client: 200 OK with updated rule
    else Rule not found
        API-->>Client: 404 Not Found
    end
    Note over API,Client: On error, API returns 500 Internal Server Error
Loading
sequenceDiagram
    participant Client
    participant API
    participant Auth
    participant DB

    Client->>API: POST /v1/resource-relationship-rules
    API->>Auth: Authenticate & Authorize (ResourceRelationshipRuleCreate)
    Auth-->>API: Success/Failure
    API->>DB: Check for existing rule (by unique fields)
    DB-->>API: Rule found/not found
    alt Rule exists
        API-->>Client: 409 Conflict with error message
    else No existing rule
        API->>DB: Transaction: Insert rule and metadata
        DB-->>API: Created rule & metadata
        API-->>Client: 200 OK with created rule
    end
    Note over API,Client: On error, API returns 500 Internal Server Error
Loading

Suggested reviewers

  • zacharyblasczyk

Poem

🐇✨
A hop and a patch, new rules in the air,
With metadata equals, relationships declare.
POST now creates, PATCH updates with care,
Permissions expanded, RBAC aware.
Tests leap ahead, migrations align—
The warren’s relationship rules now truly shine!
✨🐇


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67636e5 and e5d5338.

📒 Files selected for processing (1)
  • e2e/api/schema.ts (9 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • 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.

Support

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

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

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai 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.

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

🔭 Outside diff range comments (2)
openapi.v1.json (2)

3148-3161: 🛠️ Refactor suggestion

Add tags and security to POST create endpoint

The POST /v1/resource-relationship-rules route registers new rules but currently has no tags or security definitions, making it untracked for RBAC. Align it with other relationship endpoints.

@@ -3148,7 +3148,9 @@
     "/v1/resource-relationship-rules": {
       "post": {
         "summary": "Create a resource relationship rule",
+        "tags": ["Resource Relationships"],
+        "security": [{ "bearerAuth": [] }],
         "operationId": "createResourceRelationshipRule",
         "requestBody": {
           "required": true,

5919-5922: ⚠️ Potential issue

Specify uuid format for workspaceId in Create schema

The CreateResourceRelationshipRule schema’s workspaceId property lacks format: "uuid", unlike other schemas, which may lead to inconsistent validation.

@@ -5919,3 +5919,5 @@
           "workspaceId": {
-            "type": "string"
+            "type": "string",
+            "format": "uuid"
           },
🧹 Nitpick comments (7)
apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/route.ts (1)

124-130: Bubble up precise error codes

The catch-all 500 hides the root cause (e.g. FK violation, permission logic bug).
Return the error code only for truly unexpected errors and surface expected DB errors with 4xx codes so clients can act accordingly.
(Example: unique-index violation → 409 Conflict.)

packages/db/src/queries/get-resource-parents.ts (2)

122-152: Typo: relatipnshipTargetsrelationshipTargets

The misspelling leaks into the exported object (getTargetsWithMetadata).
While TypeScript will still allow property access, the typo hurts discoverability and IDE autocomplete.

-const relatipnshipTargets = async () => ...
-
-return {
-  relationships: ...,
-  getTargetsWithMetadata: relatipnshipTargets,
-};
+const relationshipTargets = async () => ...
+
+return {
+  relationships: ...,
+  getTargetsWithMetadata: relationshipTargets,
+};

20-40: Potential performance issue with exists sub-queries

isMetadataMatchSatisfied and isMetadataEqualsSatisfied invoke correlated EXISTS sub-queries for every candidate rule.
For workspaces with many resources & rules this can degrade quickly.

Options:

  1. Pre-aggregate metadata into a CTE and join once.
  2. Add composite indexes on (resource_id, key [, value]) to accelerate the look-ups.

Worth benchmarking before this hits production.

e2e/tests/api/resource-relationships.spec.ts (1)

129-184: Mind the HTTP semantics – consider asserting 201 Created

The POST endpoints currently return 200 OK.
REST conventions usually favour 201 Created for successful creations and include a Location header.
Aligning with the standard improves API predictability and helps client generators.

If the backend is updated, remember to adjust the test assertions:

-expect(resourceRelationship.response.status).toBe(200);
+expect(resourceRelationship.response.status).toBe(201);
apps/webservice/src/app/api/v1/resource-relationship-rules/openapi.ts (2)

25-34: Prefer 201 for successful “create” operations

The endpoint represents a pure “create” action, yet the spec still returns 200 instead of the more semantically-accurate 201 Created (with an optional Location header). Aligning the response code with REST conventions helps client tooling and reduces ambiguity around upsert vs create semantics.

-          "200": {
+          "201": {
             description: "Resource relationship rule created successfully",

35-47: Consider re-using an error schema component

The inline schema for the 409 response repeats the { error: string } pattern already used elsewhere (e.g. 500). Duplicating this in many operations inflates the spec and soon diverges. Extract a reusable ErrorResponse component and $ref it here.

apps/webservice/src/app/api/v1/resource-relationship-rules/route.ts (1)

50-56: Return 201 & Location header after successful insert

After a successful transaction we’re creating a brand-new rule. Responding with 201 and Location: /v1/resource-relationship-rules/{id} keeps the API self-describing and aligns with REST & the updated OpenAPI suggestion.

-        return NextResponse.json(rule);
+        return NextResponse.json(rule, { status: 201, headers: { Location: `/v1/resource-relationship-rules/${rule.id}` } });
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ba3194 and c0478d0.

📒 Files selected for processing (16)
  • apps/webservice/src/app/api/v1/jobs/[jobId]/get-job.ts (1 hunks)
  • apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/openapi.ts (1 hunks)
  • apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/route.ts (1 hunks)
  • apps/webservice/src/app/api/v1/resource-relationship-rules/openapi.ts (5 hunks)
  • apps/webservice/src/app/api/v1/resource-relationship-rules/route.ts (1 hunks)
  • e2e/api/schema.ts (9 hunks)
  • e2e/tests/api/resource-relationships.spec.ts (4 hunks)
  • openapi.v1.json (8 hunks)
  • packages/api/src/router/resource-relationship-rules.ts (1 hunks)
  • packages/auth/src/utils/rbac.ts (3 hunks)
  • packages/db/drizzle/0099_dear_korg.sql (1 hunks)
  • packages/db/drizzle/meta/_journal.json (1 hunks)
  • packages/db/src/queries/get-resource-parents.ts (2 hunks)
  • packages/db/src/schema/rbac.ts (1 hunks)
  • packages/db/src/schema/resource-relationship-rule.ts (4 hunks)
  • packages/validators/src/auth/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/api/src/router/resource-relationship-rules.ts
  • packages/db/src/schema/rbac.ts
  • packages/validators/src/auth/index.ts
  • apps/webservice/src/app/api/v1/jobs/[jobId]/get-job.ts
  • apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/route.ts
  • packages/auth/src/utils/rbac.ts
  • apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/openapi.ts
  • apps/webservice/src/app/api/v1/resource-relationship-rules/openapi.ts
  • e2e/api/schema.ts
  • packages/db/src/schema/resource-relationship-rule.ts
  • apps/webservice/src/app/api/v1/resource-relationship-rules/route.ts
  • e2e/tests/api/resource-relationships.spec.ts
  • packages/db/src/queries/get-resource-parents.ts
🧠 Learnings (1)
packages/db/src/schema/rbac.ts (1)
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#181
File: packages/auth/src/utils/rbac.ts:102-118
Timestamp: 2024-10-29T02:05:46.185Z
Learning: The `releaseChannel` scope type is included in the `scopeType` enum in `packages/db/src/schema/rbac.ts`.
🧬 Code Graph Analysis (4)
apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/route.ts (5)
packages/logger/src/index.ts (1)
  • logger (48-48)
packages/db/src/common.ts (2)
  • Tx (22-22)
  • takeFirst (9-13)
apps/webservice/src/app/api/v1/middleware.ts (1)
  • request (24-53)
apps/webservice/src/app/api/v1/auth.ts (2)
  • authn (15-21)
  • authz (23-58)
packages/auth/src/utils/can.ts (1)
  • can (50-53)
packages/auth/src/utils/rbac.ts (4)
packages/db/src/client.ts (1)
  • db (15-15)
packages/db/src/schema/resource-relationship-rule.ts (1)
  • resourceRelationshipRule (56-87)
packages/db/src/schema/workspace.ts (1)
  • workspace (18-27)
packages/db/src/common.ts (1)
  • takeFirst (9-13)
apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/openapi.ts (1)
apps/webservice/src/app/api/v1/resource-relationship-rules/openapi.ts (1)
  • openapi (3-163)
packages/db/src/queries/get-resource-parents.ts (1)
packages/db/src/common.ts (1)
  • Tx (22-22)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (16)
packages/db/src/schema/rbac.ts (1)

35-55: LGTM: Clean addition of the new scope type

The addition of the "resourceRelationshipRule" scope type to the enum is clean and properly aligned with the existing pattern. This enables RBAC authorization flows for resource relationship rules, which is consistent with the retrieved learning about how scope types are managed in this system.

packages/db/drizzle/meta/_journal.json (1)

697-704: LGTM: Properly formatted migration journal entry

The new migration entry (index 99) follows the established pattern and correctly documents the database schema changes for the resource_relationship_rule_metadata_equals table.

packages/api/src/router/resource-relationship-rules.ts (1)

21-21: LGTM: Properly added eager loading for metadataEquals

The modification correctly extends the query to eagerly load both metadataMatches and the newly introduced metadataEquals related entities, which is necessary for the enhanced metadata matching capabilities.

apps/webservice/src/app/api/v1/jobs/[jobId]/get-job.ts (1)

4-4: LGTM: Clean import path refactoring

The import path has been improved from a direct relative path to a cleaner package import. This is a good practice that improves maintainability.

packages/validators/src/auth/index.ts (1)

71-73: Appropriate permission additions for resource relationship rule operations.

These new permission constants follow the established naming convention and are correctly positioned with other resource-related permissions. They enable fine-grained access control for the separate create, update, and delete operations on resource relationship rules.

packages/db/drizzle/0099_dear_korg.sql (3)

1-6: Well-structured database table for metadata key-value matching.

The new table resource_relationship_rule_metadata_equals is properly designed with UUID primary key, appropriate columns for keys and values, and follows database normalization principles.


8-8: Proper foreign key constraint with cascade delete.

The foreign key relationship to the resource_relationship_rule table is correctly set up with cascade delete to ensure referential integrity when a parent rule is deleted.


9-9: Appropriate unique constraint for preventing duplicate keys.

The unique index on resource_relationship_rule_id and key prevents duplicate keys for the same rule, which maintains data integrity.

packages/auth/src/utils/rbac.ts (2)

252-270: Well-implemented RBAC scope handler for resource relationship rules.

The getResourceRelationshipRuleScopes function correctly follows the established pattern for other scope types by:

  1. Querying the database to fetch the relationship rule and its associated workspace
  2. Returning a properly structured array of scope objects for permission checking

The implementation maintains consistency with the rest of the codebase.


500-500: Appropriate registration of the new scope handler.

The new scope handler is correctly registered in the scopeHandlers record, making it available for RBAC permission checks throughout the application.

apps/webservice/src/app/api/v1/resource-relationship-rules/[ruleId]/openapi.ts (2)

3-106: Well-structured OpenAPI specification for the PATCH endpoint.

The OpenAPI specification for updating resource relationship rules via PATCH is well-defined with:

  1. Clear schema for the update payload with all appropriate fields
  2. Proper path parameter definition for ruleId
  3. Comprehensive response specifications for success and error cases
  4. Support for the new metadata matching capabilities

This aligns well with the REST API best practices by using PATCH for partial updates of a resource.


25-39: Complete metadata matching capabilities in the API schema.

The schema properly includes both metadataKeysMatch (string array) and metadataKeysEquals (key-value object array) to support the enhanced metadata matching functionality. The required properties for metadataKeysEquals items are correctly specified.

apps/webservice/src/app/api/v1/resource-relationship-rules/openapi.ts (1)

138-148: Nice consistency with the “create” schema

Requiring key and value in the array items prevents malformed objects from slipping through. 👍

apps/webservice/src/app/api/v1/resource-relationship-rules/route.ts (2)

20-21: Pass a validation schema, not a DB table definition

parseBody expects a Zod schema. Ensure that schema.createResourceRelationshipRule is a Zod validator, not the Drizzle table. If it is the latter, validation will silently be skipped and typing will be misleading.


31-48: Duplicate-detection criteria may be too lenient

The uniqueness check only matches workspaceId, reference, and dependencyType.
Two rules with different sourceKind/Version but identical reference + dependency type would now be blocked even though they act on different resources. Confirm that this is intentional business logic – otherwise extend the predicate.

e2e/api/schema.ts (1)

1276-1291: Generated types expose optional key / value

Because the base spec omits required, generated TS types make both fields optional. Once the spec is fixed (see openapi.ts comment), regenerate the SDK to tighten these types.

Comment on lines +32 to +41
const metadataKeys = _.uniq(metadataKeysMatch ?? []);
if (metadataKeys.length > 0)
await tx.insert(schema.resourceRelationshipRuleMetadataMatch).values(
metadataKeys.map((key) => ({
resourceRelationshipRuleId: ruleId,
key,
})),
);

return metadataKeys;
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

Duplicate key handling may silently drop conflicting values

_.uniq(metadataKeysMatch) (and uniqBy below) keeps only the first occurrence.
If the client unintentionally supplies the same key twice with different values in metadataKeysEquals, one of them will be discarded without warning.

Consider:

  • validating in Zod that keys are unique and consistent, returning 400 when conflicts are detected, or
  • merging duplicates deterministically and logging a warning so operators understand why data disappeared.

Comment on lines +100 to +107
const rule = await db.transaction(async (tx) => {
const rule = await tx
.update(schema.resourceRelationshipRule)
.set(body)
.where(eq(schema.resourceRelationshipRule.id, ruleId))
.returning()
.then(takeFirst);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid passing non-column & undefined fields into .set()

update().set(body) receives the entire parsed payload, which includes:

  • properties that do not exist on resourceRelationshipRule (metadataKeysMatch, metadataKeysEquals)
  • properties that may be undefined (because the schema is partial())

Drizzle will happily accept the object at runtime but will try to write all keys.
This can surface as:

  • TS compilation errors (extra keys) that get silenced with any
  • SQL errors such as “column metadata_keys_match does not exist”
  • Columns overwritten with NULL / DEFAULT when undefined sneaks in

Sanitise the payload before issuing the update:

-const rule = await tx
-  .update(schema.resourceRelationshipRule)
-  .set(body)
+const updateData = _.omitBy(
+  _.omit(body, ["metadataKeysMatch", "metadataKeysEquals"]),
+  _.isUndefined,
+) as Partial<typeof schema.resourceRelationshipRule.$inferInsert>;
+
+const rule = await tx
+  .update(schema.resourceRelationshipRule)
+  .set(updateData)
📝 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
const rule = await db.transaction(async (tx) => {
const rule = await tx
.update(schema.resourceRelationshipRule)
.set(body)
.where(eq(schema.resourceRelationshipRule.id, ruleId))
.returning()
.then(takeFirst);
const rule = await db.transaction(async (tx) => {
// sanitize: drop non-columns and undefined fields
const updateData = _.omitBy(
_.omit(body, ["metadataKeysMatch", "metadataKeysEquals"]),
_.isUndefined,
) as Partial<typeof schema.resourceRelationshipRule.$inferInsert>;
const rule = await tx
.update(schema.resourceRelationshipRule)
.set(updateData)
.where(eq(schema.resourceRelationshipRule.id, ruleId))
.returning()
.then(takeFirst);
return rule;
});

Comment on lines 162 to +174
export const createResourceRelationshipRule = createInsertSchema(
resourceRelationshipRule,
);
)
.omit({ id: true })
.extend({
metadataKeysMatch: z.array(z.string()).optional(),
metadataKeysEquals: z
.array(z.object({ key: z.string(), value: z.string() }))
.optional(),
});

export const updateResourceRelationshipRule =
createResourceRelationshipRule.partial();
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

Schema allows divergent metadataKeysEquals duplicates client-side

metadataKeysEquals is only validated as an array of objects; nothing enforces unique keys or non-empty arrays.
Given the table has a unique index on (ruleId, key) the server will throw 23505 violations for duplicates that the API layer could have caught.

Add refinement to the Zod schema:

 .extend({
   metadataKeysMatch: z.array(z.string()).optional(),
-  metadataKeysEquals: z
-      .array(z.object({ key: z.string(), value: z.string() }))
-      .optional(),
+  metadataKeysEquals: z
+    .array(z.object({ key: z.string(), value: z.string() }))
+    .optional()
+    .refine(
+      (arr) => _.uniqBy(arr, "key").length === arr.length,
+      "metadataKeysEquals must contain unique keys",
+    ),
 })
📝 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
export const createResourceRelationshipRule = createInsertSchema(
resourceRelationshipRule,
);
)
.omit({ id: true })
.extend({
metadataKeysMatch: z.array(z.string()).optional(),
metadataKeysEquals: z
.array(z.object({ key: z.string(), value: z.string() }))
.optional(),
});
export const updateResourceRelationshipRule =
createResourceRelationshipRule.partial();
export const createResourceRelationshipRule = createInsertSchema(
resourceRelationshipRule,
)
.omit({ id: true })
.extend({
metadataKeysMatch: z.array(z.string()).optional(),
metadataKeysEquals: z
.array(z.object({ key: z.string(), value: z.string() }))
.optional()
.refine(
(arr) => _.uniqBy(arr, "key").length === arr.length,
"metadataKeysEquals must contain unique keys",
),
});
export const updateResourceRelationshipRule =
createResourceRelationshipRule.partial();

Comment on lines 94 to 107
metadataKeysMatch: {
type: "array",
items: { type: "string" },
},
metadataKeysEquals: {
type: "array",
items: {
type: "object",
properties: {
key: { type: "string" },
value: { type: "string" },
},
},
},
Copy link
Contributor

@coderabbitai coderabbitai bot May 6, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

metadataKeysEquals items lack required fields – may generate loose types

In ResourceRelationshipRule, the key & value fields inside each metadataKeysEquals item are optional because no required array is defined.
This differs from CreateResourceRelationshipRule, where both are marked required, and results in generated typings that allow {} items at runtime.

           metadataKeysEquals: {
             type: "array",
             items: {
               type: "object",
               properties: {
                 key: { type: "string" },
                 value: { type: "string" },
               },
+              required: ["key", "value"],
             },
           },

Copy link
Member Author

Choose a reason for hiding this comment

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

make a commit sugestion

Copy link
Contributor

Choose a reason for hiding this comment

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

@jsbroks Here's the commit suggestion:

import type { Swagger } from "atlassian-openapi";

export const openapi: Swagger.SwaggerV3 = {
  // content remains unchanged...
  components: {
    schemas: {
      // content remains unchanged...
      ResourceRelationshipRule: {
        type: "object",
        properties: {
          // other properties remain unchanged...
          metadataKeysEquals: {
            type: "array",
            items: {
              type: "object",
              properties: {
                key: { type: "string" },
                value: { type: "string" },
              },
+              required: ["key", "value"],
            },
          },
        },
        required: [
          // required fields remain unchanged...
        ],
      },
      // content remains unchanged...
    },
  },
};

This change ensures that both key and value fields are required in each metadataKeysEquals item, matching the pattern used in the CreateResourceRelationshipRule schema and preventing loosely typed objects.

Comment on lines 68 to 81
const metadataKeysEquals = _.uniqBy(
body.metadataKeysEquals ?? [],
(m) => m.key,
);
}

return rule;
},
);
if (metadataKeysEquals.length > 0)
await tx
.insert(schema.resourceRelationshipRuleMetadataEquals)
.values(
metadataKeysEquals.map((m) => ({
resourceRelationshipRuleId: rule.id,
key: m.key,
value: m.value,
})),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Silently dropping duplicates by key may hide conflicting values

_.uniqBy(body.metadataKeysEquals ?? [], m => m.key) discards later items with the same key but different values.
That behaviour can mask user errors. Instead, detect conflicts and return 400 with a clear message.

-const metadataKeysEquals = _.uniqBy(body.metadataKeysEquals ?? [], (m) => m.key);
+const equals = body.metadataKeysEquals ?? [];
+const duplicateKeys = _(equals).groupBy("key").pickBy(v => v.length > 1).keys().value();
+if (duplicateKeys.length)
+  throw new Error(`Duplicate metadataKeysEquals detected for keys: ${duplicateKeys.join(", ")}`);
+const metadataKeysEquals = equals;

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

Comment on lines 5885 to 5904
"metadataKeysMatch": {
"type": "array",
"items": {
"type": "string"
}
},
"metadataKeysEquals": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enforce required properties on metadataKeysEquals items

In the ResourceRelationshipRule schema, the metadataKeysEquals items define properties but omit a required array, allowing malformed objects. This should mirror the item schema in CreateResourceRelationshipRule/UpdateResourceRelationshipRule.

@@ -5885,7 +5885,10 @@
           "metadataKeysEquals": {
             "type": "array",
             "items": {
-              "type": "object",
+              "type": "object",
               "properties": {
                 "key": { "type": "string" },
                 "value": { "type": "string" }
+              },
+              "required": ["key", "value"]
             }
           }
📝 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
"metadataKeysMatch": {
"type": "array",
"items": {
"type": "string"
}
},
"metadataKeysEquals": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
}
"metadataKeysMatch": {
"type": "array",
"items": {
"type": "string"
}
},
"metadataKeysEquals": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": ["key", "value"]
}
}

Comment on lines +3073 to +3096
"/v1/resource-relationship-rules/{ruleId}": {
"patch": {
"summary": "Update a resource relationship rule",
"operationId": "updateResourceRelationshipRule",
"parameters": [
{
"name": "ruleId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateResourceRelationshipRule"
}
}
}
},
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

Missing requestBody required flag and security definitions for PATCH endpoint

The PATCH /v1/resource-relationship-rules/{ruleId} endpoint lacks "required": true on its requestBody (all other PATCH operations define this) and omits tags and security metadata for RBAC enforcement.

Proposed diff:

@@ -3073,8 +3073,13 @@
-    "/v1/resource-relationship-rules/{ruleId}": {
-      "patch": {
+    "/v1/resource-relationship-rules/{ruleId}": {
+      "patch": {
         "summary": "Update a resource relationship rule",
         "operationId": "updateResourceRelationshipRule",
+        "tags": ["Resource Relationships"],
+        "security": [{ "bearerAuth": [] }],
+        "requestBody": {
+          "required": true,
           "content": {
             "application/json": {
               "schema": {
📝 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
"/v1/resource-relationship-rules/{ruleId}": {
"patch": {
"summary": "Update a resource relationship rule",
"operationId": "updateResourceRelationshipRule",
"parameters": [
{
"name": "ruleId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateResourceRelationshipRule"
}
}
}
},
"/v1/resource-relationship-rules/{ruleId}": {
"patch": {
"summary": "Update a resource relationship rule",
"operationId": "updateResourceRelationshipRule",
"tags": ["Resource Relationships"],
"security": [{ "bearerAuth": [] }],
"parameters": [
{
"name": "ruleId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateResourceRelationshipRule"
}
}
}
},

@jsbroks jsbroks merged commit b5475ea into main May 6, 2025
8 of 10 checks passed
@jsbroks jsbroks deleted the add-metadata-key-matching branch May 6, 2025 02:28
@coderabbitai coderabbitai bot mentioned this pull request Oct 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants