Skip to content

refactor: move openapi to separate graph#637

Merged
looplj merged 1 commit into
unstablefrom
development
Jan 21, 2026
Merged

refactor: move openapi to separate graph#637
looplj merged 1 commit into
unstablefrom
development

Conversation

@looplj

@looplj looplj commented Jan 21, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Added OpenAPI GraphQL endpoint at /openapi/v1/graphql with LLM API key creation support
    • Introduced dedicated playground interface for OpenAPI GraphQL operations
  • Refactor

    • Restructured API key authentication and authorization flow
    • Reorganized routes with dedicated admin and OpenAPI prefixes
  • Chores

    • Updated gqlgen dependency to v0.17.86
    • Enhanced code generation tooling

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 21, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR restructures the LLM API key creation flow by establishing a dedicated OpenAPI GraphQL endpoint at /openapi/v1/graphql while removing the createLLMAPIKey mutation from the primary GraphQL schema. It consolidates API key validation logic, introduces OpenAPI-specific schema definitions and resolvers, and refactors authentication middleware to distinguish between standard and OpenAPI authentication paths.

Changes

Cohort / File(s) Summary
Build & Dependency Configuration
Makefile, go.mod
Added generate-openapi Makefile target for code generation in the openapi subdirectory; bumped gqlgen to v0.17.86 and added cli/v3 dependency with gqlgen tool directive.
API Key Service Logic
internal/server/biz/api_key.go, internal/server/biz/api_key_test.go
Removed owner nil checks and scope validations from CreateLLMAPIKey; simplified name trimming; rewrote tests to use dedicated setup context, added unauthorized API key scenario, and replaced nil owner/non-service-account checks with unauthorized key validation.
Main GraphQL Schema Removal
internal/server/gql/axonhub.graphql, internal/server/gql/axonhub.resolvers.go, internal/server/gql/generated.go
Deleted createLLMAPIKey mutation from primary schema; removed resolver implementation and generated code artifacts (102 lines removed from generated.go); retained commented warning block in resolvers.go.
New OpenAPI GraphQL Endpoint
internal/server/gql/openapi/generate.go, internal/server/gql/openapi/gqlgen.yml, internal/server/gql/openapi/graphql.go, internal/server/gql/openapi/models_gen.go, internal/server/gql/openapi/openapi.graphql, internal/server/gql/openapi/openapi.resolvers.go, internal/server/gql/openapi/resolver.go
Created isolated OpenAPI GraphQL package with dedicated schema (APIKey type, createLLMAPIKey mutation), resolver implementation, DI wiring via Resolver struct, and GraphQL handler factory with introspection and persisted-query extensions; schema configuration via gqlgen.yml.
Authentication Middleware
internal/server/middleware/auth.go, internal/server/middleware/graphql_auth_test.go
Introduced WithOpenAPIAuth middleware for API key validation with service account enforcement; removed WithGraphQLAuthForLLMAPIKey and associated JWT fallback logic; deleted graphql_auth_test.go (124 lines, 2 test cases).
Route & Server Integration
internal/server/routes.go, internal/server/server.go
Registered OpenAPI GraphQL handler; added routes /openapi/v1/graphql (POST) and /openapi/v1/playground (GET); moved admin GraphQL to /admin/graphql; imported openapi package in server.go constructor setup.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Router
    participant AuthMW as Auth Middleware
    participant GraphQL as GraphQL Handler
    participant APIKeyService
    participant DB as Database

    Note over Client,DB: OLD FLOW: Main GraphQL
    Client->>Router: POST /graphql (createLLMAPIKey)
    Router->>AuthMW: WithGraphQLAuthForLLMAPIKey
    AuthMW->>AuthMW: Parse GraphQL payload for mutation type
    AuthMW->>GraphQL: Validate & forward context
    GraphQL->>GraphQL: Resolve createLLMAPIKey
    GraphQL->>APIKeyService: CreateLLMAPIKey(ownerKey, name)
    APIKeyService->>DB: Create & persist
    DB-->>APIKeyService: APIKey
    APIKeyService-->>GraphQL: APIKey
    GraphQL-->>Client: APIKey (or error)

    Note over Client,DB: NEW FLOW: OpenAPI GraphQL
    Client->>Router: POST /openapi/v1/graphql (createLLMAPIKey)
    Router->>AuthMW: WithOpenAPIAuth
    AuthMW->>AuthMW: Validate API key from Authorization header
    AuthMW->>AuthMW: Enforce service account type
    AuthMW->>GraphQL: Set context (APIKey, ProjectID)
    GraphQL->>GraphQL: Resolve createLLMAPIKey
    GraphQL->>APIKeyService: CreateLLMAPIKey(ownerKey, name)
    APIKeyService->>DB: Create & persist (no validations)
    DB-->>APIKeyService: APIKey
    APIKeyService-->>GraphQL: APIKey
    GraphQL-->>Client: APIKey (or error)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Feat/llm api key create #633: Directly related as it modifies the same createLLMAPIKey feature—including APIKey service method, GraphQL resolver, and authentication middleware wiring that this PR refactors and relocates.

Poem

🐰 A rabbit's ode to the OpenAPI leap:
GraphQL flows split, two paths now run deep,
API keys dance on /openapi ground,
Old mutations fade, new schemas abound,
Middleware whispers "Bearer" instead,
A tidier garden where handlers are fed! 🌱✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main refactoring objective: moving OpenAPI functionality to a separate GraphQL graph, which is reflected throughout the changeset.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @looplj, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request undertakes a significant architectural refactoring by isolating the OpenAPI-related GraphQL functionality into its own dedicated graph. This separation encompasses distinct schema definitions, resolver implementations, code generation, and a specialized authentication middleware. The change enhances modularity, clarifies the purpose of different API endpoints, and allows for more granular control over access to specific mutations like createLLMAPIKey, which is now exclusively accessible via the new OpenAPI endpoint using service account API keys.

Highlights

  • Separate OpenAPI GraphQL Graph: The createLLMAPIKey mutation has been refactored from the main GraphQL schema into a new, dedicated OpenAPI GraphQL schema (openapi.graphql), promoting better modularity.
  • Dedicated OpenAPI Endpoint: A new GraphQL endpoint (/openapi/v1/graphql) and its corresponding playground (/openapi/v1/playground) have been established, specifically for the OpenAPI graph, allowing for distinct routing and access policies.
  • Refactored Authentication Middleware: The authentication logic for the OpenAPI endpoint has been updated and moved to a new WithOpenAPIAuth middleware, which now strictly enforces service account API key authentication and handles relevant validation previously present in the business logic.
  • Updated Build Process: The Makefile now includes a generate-openapi target to manage the code generation for the newly separated OpenAPI GraphQL graph, ensuring its independent build.
  • API Key Service Streamlining: Internal validation checks within the APIKeyService.CreateLLMAPIKey function have been removed, as these concerns are now handled by the new authentication middleware and privacy rules, simplifying the business logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

The pull request successfully refactors the OpenAPI GraphQL endpoint into a separate graph, which enhances modularity and separation of concerns. The createLLMAPIKey mutation has been correctly moved to this new graph. However, there are critical concerns regarding the authorization logic for this new endpoint. The explicit scope check for ScopeWriteAPIKeys, previously in the business logic, is now missing in the new middleware setup, posing a security risk. Additionally, some minor improvements can be made to clarity in the Makefile and test coverage for the new authorization middleware.

I am having trouble creating individual review comments. Click here to see my feedback.

internal/server/middleware/auth.go (107-109)

critical

The WithOpenAPIAuth middleware correctly enforces that only TypeServiceAccount API keys can access the OpenAPI endpoints. However, it does not verify if the service account has the necessary ScopeWriteAPIKeys permission to create LLM API keys. This is a critical security oversight, as any service account could potentially create LLM API keys without proper authorization. You will also need to add import "slices" to the file.

        if apiKey.Type != apikey.TypeServiceAccount {
            AbortWithError(c, http.StatusUnauthorized, errors.New("API key type must be service account"))
            return
        }
        if !slices.Contains(apiKey.Scopes, string(scopes.ScopeWriteAPIKeys)) {
            AbortWithError(c, http.StatusUnauthorized, errors.New("API key missing required scope: write_api_keys"))
            return
        }

internal/server/middleware/graphql_auth_test.go (1-124)

high

The graphql_auth_test.go file was removed, which contained tests for the previous GraphQL authorization middleware. With the introduction of WithOpenAPIAuth middleware and the relocation of authorization logic, it is crucial to create new dedicated tests for WithOpenAPIAuth to ensure its correctness, especially for the critical scope checks (e.g., ScopeWriteAPIKeys for service accounts).

Makefile (13)

medium

The echo message for the generate-openapi target is generic. To improve clarity, it should be more specific to reflect that it's generating OpenAPI-related GraphQL code.

  @echo "Generating OpenAPI GraphQL code..."

internal/server/biz/api_key.go (8)

medium

The slices import was removed from this file. This indicates that the slices.Contains check for ScopeWriteAPIKeys is intended to be handled elsewhere, likely in the middleware. Please ensure this critical scope validation is correctly implemented in the WithOpenAPIAuth middleware to prevent unauthorized API key creation.

internal/server/routes.go (28)

medium

The APIKey *api.APIKeyHandlers field was removed from the Handlers struct. Please confirm if this handler was intentionally removed and if its functionality is no longer required or has been fully replaced by other handlers. If it provided other API key related endpoints, those might now be missing.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/server/biz/api_key.go (1)

64-81: Add nil check for owner parameter to follow defensive programming practices.

The owner parameter is dereferenced at lines 80-81 without a nil check. While the single active call site in openapi.resolvers.go validates the parameter before calling this function, the method should validate its own inputs rather than relying on caller validation. This prevents potential nil pointer panics if the function is called from other locations in the future.

Proposed fix
 func (s *APIKeyService) CreateLLMAPIKey(ctx context.Context, owner *ent.APIKey, name string) (*ent.APIKey, error) {
+	if owner == nil {
+		return nil, fmt.Errorf("owner API key is required")
+	}
+
 	name = strings.TrimSpace(name)
 	if name == "" {
 		return nil, ErrAPIKeyNameRequired
 	}
🧹 Nitpick comments (7)
internal/server/gql/axonhub.resolvers.go (1)

510-526: Consider removing the legacy “harms way” block once migration is complete.

It’s harmless, but leaving commented resolver logic in a generated file can confuse future edits and tooling. If the OpenAPI graph is now the sole owner, it’s safe to delete this block to keep the generated file clean.

🧹 Suggested cleanup
-// !!! WARNING !!!
-// The code below was going to be deleted when updating resolvers. It has been copied here so you have
-// one last chance to move it out of harms way if you want. There are two reasons this happens:
-//  - When renaming or deleting a resolver the old code will be put in here. You can safely delete
-//    it when you're done.
-//  - You have helper methods in this file. Move them out to keep these resolver files clean.
-/*
-    func (r *mutationResolver) CreateLLMAPIKey(ctx context.Context, name string) (*ent.APIKey, error) {
-        ownerKey, ok := contexts.GetAPIKey(ctx)
-        if !ok || ownerKey == nil {
-            return nil, fmt.Errorf("api key not found in context")
-        }
-
-        return r.apiKeyService.CreateLLMAPIKey(ctx, ownerKey, name)
-    }
-*/
Makefile (1)

12-15: Consider updating the echo message for clarity.

The message says "Generating GraphQL and Ent code..." but this target only generates OpenAPI GraphQL code, not Ent schemas. A more accurate message would help avoid confusion.

Suggested improvement
 generate-openapi:
-	`@echo` "Generating GraphQL and Ent code..."
+	`@echo` "Generating OpenAPI GraphQL code..."
 	cd internal/server/gql/openapi && go generate
-	`@echo` "Generation completed!"
+	`@echo` "OpenAPI GraphQL generation completed!"
internal/server/gql/openapi/gqlgen.yml (1)

7-10: Remove commented-out configuration.

The commented exec section is unused and adds noise. Consider removing it to keep the configuration clean.

Suggested cleanup
 omit_gqlgen_version_in_file_notice: true

-#exec:
-#  layout: follow-schema
-#  dir: ./graph
-#  package: graph
-
 # resolver reports where the resolver implementations go.
internal/server/middleware/auth.go (2)

87-88: Outdated comment doesn't match the function's purpose.

The comment states "allows API key auth for createLLMAPIKey only" but the function is a general OpenAPI authentication middleware that restricts access to service account API keys. Consider updating the comment to accurately describe the middleware's purpose.

📝 Suggested comment update
-// WithOpenAPIAuth allows API key auth for createLLMAPIKey only.
+// WithOpenAPIAuth middleware validates API key authentication for OpenAPI endpoints,
+// restricting access to service account type API keys only.
 func WithOpenAPIAuth(auth *biz.AuthService) gin.HandlerFunc {

89-119: Consider reducing code duplication with WithAPIKeyConfig.

WithOpenAPIAuth shares most of its logic with WithAPIKeyConfig (API key extraction, authentication, error handling, context propagation). You could refactor to compose the middleware or extract common logic into a helper function.

♻️ One possible approach
// Helper to handle common API key authentication logic
func authenticateAndSetContext(c *gin.Context, auth *biz.AuthService, config *APIKeyConfig) (*ent.APIKey, error) {
    key, err := ExtractAPIKeyFromRequest(c.Request, config)
    if err != nil {
        AbortWithError(c, http.StatusUnauthorized, err)
        return nil, err
    }

    apiKey, err := auth.AnthenticateAPIKey(c.Request.Context(), key)
    if err != nil {
        if ent.IsNotFound(err) || errors.Is(err, biz.ErrInvalidAPIKey) {
            AbortWithError(c, http.StatusUnauthorized, errors.New("Invalid API key"))
        } else {
            AbortWithError(c, http.StatusInternalServerError, errors.New("Failed to validate API key"))
        }
        return nil, err
    }
    return apiKey, nil
}

Then WithOpenAPIAuth can call this helper and add the service account check.

internal/server/gql/openapi/openapi.graphql (1)

7-9: Consider adding schema documentation.

The mutation signature is correct. For better API discoverability, consider adding GraphQL descriptions to document the mutation's purpose and requirements.

📝 Example with documentation
"""
Creates a new LLM API key associated with the authenticated service account's project.
Requires a service account API key with write_api_keys scope for authentication.
"""
extend type Mutation {
  createLLMAPIKey(
    """The display name for the new API key."""
    name: String!
  ): APIKey!
}
internal/server/gql/openapi/openapi.resolvers.go (1)

16-32: Consider simplifying the redundant nil check.

Based on the contexts.GetAPIKey implementation (which returns container.APIKey != nil as the boolean), when ok is true, ownerKey is guaranteed to be non-nil. The ownerKey == nil check is defensive but redundant.

♻️ Suggested simplification
 func (r *mutationResolver) CreateLLMAPIKey(ctx context.Context, name string) (*APIKey, error) {
 	ownerKey, ok := contexts.GetAPIKey(ctx)
-	if !ok || ownerKey == nil {
+	if !ok {
 		return nil, fmt.Errorf("api key not found in context")
 	}

@looplj looplj merged commit b63fee3 into unstable Jan 21, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jan 21, 2026
looplj added a commit that referenced this pull request Mar 1, 2026
looplj pushed a commit that referenced this pull request May 9, 2026
…#1617)

* feat(openapi): add updateAPIKeyProfiles and loadApiKeyProfileTemplate

Adds two GraphQL mutations to /openapi/v1/graphql for programmatic
LLM API key profile management via service account keys:

- updateAPIKeyProfiles: integral state replacement (mirrors admin)
- loadApiKeyProfileTemplate: append a project template to a key

Both reuse existing admin biz methods directly — zero new business
logic. Following the createLLMAPIKey pattern from #637, the OpenAPI
schema stays isolated from admin (own gqlgen module, own minimal
APIKey output type, scope checks delegated to ent privacy).

Privacy model:
- New scopes.APIKeyProjectScopeReadRule restricts API-key-principal
  reads to the caller's project, gated by required scope. Mounted on
  both APIKey and APIKeyProfileTemplate Query policies so service
  accounts can resolve the target key and template.
- scopes.UserProjectScopeReadRule now skips when no user is in context
  (matching UserProjectScopeWriteRule's existing convention) so API
  key principals fall through to APIKeyProjectScopeReadRule instead
  of being denied at the user-rule layer.

Required scopes for the new mutations: read_api_keys + write_api_keys.

Frontend compatibility fix: the OpenAPI updateAPIKeyProfiles resolver
coerces nil ModelMappings to []. Admin UI's Zod schema strictly
requires a non-null array for this single field (sibling array fields
are .nullable()), so OpenAPI clients omitting modelMappings would
otherwise produce rows the UI can't render.

Tests:
- internal/scopes: APIKeyProjectScopeReadRule unit tests
- internal/server/gql/openapi (new test file): 9 resolver-level e2e
  tests covering happy paths, cross-project denial, missing-scope
  denial, and ModelMappings normalization regression

Example client (examples/openapi/):
- synced server schema, added genqlient operations for new mutations
- regenerated client code with Decimal scalar binding
- README updated with scope requirements and capability list

* feat(openapi): expose id on APIKey output type

Add id: ID! to the OpenAPI APIKey output so callers can chain createLLMAPIKey
into updateAPIKeyProfiles / loadApiKeyProfileTemplate without a separate
lookup. Previously the only way to obtain a freshly-created LLM key's id was
via admin UI / direct DB access, which programmatic OpenAPI clients don't
have access to.

Mechanical changes:
- openapi.graphql: APIKey gains id field
- toOpenAPIAPIKey lifted into a new helper.go file so subsequent gqlgen
  regenerations don't sweep it into the warning block as unknown code
- examples/openapi/graphql: schema mirror synced; api_key.graphql operations
  now select id; genqlient client regenerated

* review(openapi): tighten privacy rule + non-null profiles list

Address greptile-apps review on PR #1617:

P2 — APIKeyProjectScopeReadRule now Denyf (was Skipf) when an API key
principal lacks the required scope. Aligns with the explicit-deny
contract used by APIKeyScopeQueryRule and APIKeyProjectScopeWriteRule.
Skip is reserved for "this rule's principal type doesn't apply"; here
the principal IS an API key, just unauthorised.

P1 — UpdateAPIKeyProfilesInput.profiles is now non-null
([APIKeyProfileInput!]!). Closes the gap where a client could submit
profiles: null and rely on biz validateActiveProfile to reject —
failing earlier at the GraphQL parse layer is the safer contract.
Diverges from admin's looser type intentionally; admin/openapi schemas
are independently versioned.

* style: gofumpt-fix trailing newline and field alignment
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