Skip to content

Update API and storage to support license attributes#11

Merged
VisualBean merged 1 commit into
mainfrom
alex/attributes
Jun 3, 2026
Merged

Update API and storage to support license attributes#11
VisualBean merged 1 commit into
mainfrom
alex/attributes

Conversation

@VisualBean

@VisualBean VisualBean commented May 30, 2026

Copy link
Copy Markdown
Contributor

This commit introduces support for storing and handling license attributes in the backend and frontend. In the backend, the licenseManagementResponse struct in internal/api/management_licenses.go is updated to include an Attributes map. Correspondingly, marshalGenerateResponseBody in internal/storage/store.go is updated to include Attributes in the response payload. The database schema in internal/storage/schema.sql is updated to add an attributes JSONB column to the licenses table. In the API layer, issueOfflineToken in internal/api/offline_tokens.go is updated to accept and use an attributes map. License handlers in internal/api/license_handlers.go are updated to handle and validate these attributes, including a new validation function validateAttributes. In the frontend, LicensesView.vue is updated to include an attributes reactive property in the form state. Similarly, SlugsView.vue updates the form state for attributes. The slugAPI in ui/src/stores/api.ts is updated to include an attributes field in the data

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for custom attributes on slug templates; attributes are inherited by generated licenses and included in offline token JWT claims.
    • Introduced license activation capability with fingerprint validation and metadata submission.
    • Extended license and slug management APIs to handle attributes in requests and responses.
  • UI Enhancements

    • Added attributes management interface for slug templates.
    • Displayed license attributes in details view.
    • Added license activation form with fingerprint and metadata fields.
  • Chores

    • Version bumped to 0.4.0.

Review Change Stack

This commit introduces support for storing and handling license attributes in the backend and frontend.
In the backend, the `licenseManagementResponse` struct in `internal/api/management_licenses.go` is updated to include an `Attributes` map. Correspondingly, `marshalGenerateResponseBody` in `internal/storage/store.go` is updated to include `Attributes` in the response payload. The database schema in `internal/storage/schema.sql` is updated to add an `attributes` JSONB column to the licenses table.
In the API layer, `issueOfflineToken` in `internal/api/offline_tokens.go` is updated to accept and use an `attributes` map. License handlers in `internal/api/license_handlers.go` are updated to handle and validate these attributes, including a new validation function `validateAttributes`.
In the frontend, `LicensesView.vue` is updated to include an `attributes` reactive property in the form state. Similarly, `SlugsView.vue` updates the form state for attributes. The `slugAPI` in `ui/src/stores/api.ts` is updated to include an `attributes` field in the data
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR extends the license server with support for attributes—arbitrary JSON metadata stored at the slug and license level. Attributes are defined when creating or updating slug templates, automatically flow into generated licenses, and are embedded into offline JWT tokens for runtime use. The implementation spans database schema, storage persistence, API contracts and validation, and full UI support for editing and viewing attributes.

Changes

License & Slug Attributes Support

Layer / File(s) Summary
Database schema and core data types
internal/storage/schema.sql, internal/storage/store.go, internal/storage/slug_management.go
Adds attributes JSONB NOT NULL DEFAULT '{}'::jsonb columns to slugs and licenses tables via CREATE TABLE and ALTER TABLE migration steps. Updates storage types to include Attributes map[string]any fields in SlugRecord, CreateSlugParams, UpdateSlugParams, GeneratedLicense, ActivationResult, ValidationResult, LicenseRow, and slugOptions.
Slug attributes persistence layer
internal/storage/slug_management.go
Updates all slug CRUD operations to handle attributes: ListSlugs and GetSlugByName now select the column, CreateSlug inserts it, UpdateSlugByName updates it with full carry-forward logic (preserving existing attributes unless explicitly overridden), and scanSlugRecord scans it into the type. Uses copyMetadata for safe marshaling.
License generation with attributes propagation
internal/storage/store.go, internal/api/license_handlers.go
loadSlugOptionsByName selects and scans slug attributes. createLicenseFromSlugOptions marshals them into the license INSERT and returns GeneratedLicense with attributes set. marshalGenerateResponseBody includes attributes in webhook payloads for both idempotent and non-idempotent generate paths.
License attributes loading and token flow
internal/storage/store.go, internal/api/license_handlers.go
loadLicenseByKey selects, scans, and unmarshals attributes. ActivateLicense and ValidateLicense populate result attributes from the loaded license. handleActivate and handleValidate pass result.Attributes to issueOfflineToken.
Offline JWT attributes support
internal/api/offline_tokens.go, internal/offlinejwt/offlinejwt.go
issueOfflineToken accepts an attributes parameter and passes it to TokenParams. SignEd25519JWT creates an "attributes" JWT claim using a defensive copyMap helper that clones or returns empty maps.
Attributes validation and API contracts
internal/api/server.go, internal/api/management_licenses.go, internal/api/management_slugs.go
Extracts common JSON-map validation into validateJSONMap(values, label) helper; validateMetadata delegates to it, and new validateAttributes function uses it. API response models (slugResponse, licenseManagementResponse) and request payloads (createSlugRequest, updateSlugRequest) gain Attributes fields.
Slug management API handlers with attributes
internal/api/management_slugs.go
handleCreateSlug validates and forwards attributes. handleUpdateSlug treats attributes as a valid update field, validates when provided, and forwards them. mapSlugResponse includes attributes in the JSON response.
License API responses and management
internal/api/management_licenses.go, internal/storage/license_management.go
mapLicenseManagementResponse sets attributes from the loaded license. ListLicenses query selects the column, and scanLicenseListRow scans and unmarshals attributes with empty-map defaults.
Frontend slug attributes UI
ui/src/stores/api.ts, ui/src/views/SlugsView.vue
Slug API types accept optional attributes in payloads. SlugsView form state includes attributes string, parseJSONObject helper validates it as a non-array JSON object, resetForm() clears it, buildPayload() includes it, and edit mode populates it from the existing slug. Template adds an "Attributes JSON" textarea.
Frontend license attributes and activation UI
ui/src/views/LicensesView.vue
Imports runtimeAPI and adds activatingLicense loading flag and activationForm state. parseJSONObject helper validates activation metadata. After reload, reselects the current license by ID or key. New activateSelectedLicense async handler validates fingerprint, calls the runtime activation API, reloads licenses, and manages state/errors. License details modal displays an "Attributes Snapshot" section and activation form.
Tests and version update
internal/api/server_test.go, internal/storage/generate_license_test.go, internal/version/version.go
Server test stubs include attributes in mock responses and decodes/validates JWT attributes claims. Generate license test defines and verifies snapshotted attributes in license generation. Version bumped from 0.2.0 to 0.4.0.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit hops through attributes bright,
From slug to license, JSON takes flight,
Tokens carry metadata with care,
Where custom data dances through the air. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding support for license attributes across the API and storage layers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alex/attributes

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 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/storage/slug_management.go (1)

162-187: ⚖️ Poor tradeoff

Unify attributes JSONB handling (map vs []byte) for predictability.

internal/storage/slug_management.go persists slugs.attributes by passing map[string]any directly (copyMetadata(params.Attributes) / copyMetadata(attributes)) and scanning JSONB straight into map[string]any (scanSlugRecord / loadSlugOptionsByName). In contrast, internal/storage/store.go persists licenses.metadata/licenses.attributes by explicitly marshaling to []byte (metadataToJSON) and unmarshaling on read (json.Unmarshal).

There’s no evidence of pgx SimpleProtocol usage in this repo, so the current map[string]any <-> jsonb path for slugs.attributes should be fine today; standardizing the approach across tables would mainly reduce future refactor risk.

copyMetadata(nil) already returns an empty map ({}), which matches slugs.attributes JSONB NOT NULL DEFAULT '{}'::jsonb.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/slug_management.go` around lines 162 - 187, Unify JSONB
handling for slugs.attributes by storing marshaled bytes and unmarshaling on
read: replace direct map passing in the INSERT (where
copyMetadata(params.Attributes) is used) with the same metadataToJSON(...) call
used for licenses, and update readers (scanSlugRecord and loadSlugOptionsByName)
to expect []byte from slugs.attributes and json.Unmarshal into a map[string]any;
keep copyMetadata(...) behavior (empty map for nil) but ensure persistence uses
metadataToJSON and reads perform json.Unmarshal for consistency with
metadata/attributes handling in store.go.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/storage/slug_management.go`:
- Around line 162-187: Unify JSONB handling for slugs.attributes by storing
marshaled bytes and unmarshaling on read: replace direct map passing in the
INSERT (where copyMetadata(params.Attributes) is used) with the same
metadataToJSON(...) call used for licenses, and update readers (scanSlugRecord
and loadSlugOptionsByName) to expect []byte from slugs.attributes and
json.Unmarshal into a map[string]any; keep copyMetadata(...) behavior (empty map
for nil) but ensure persistence uses metadataToJSON and reads perform
json.Unmarshal for consistency with metadata/attributes handling in store.go.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 089eba0b-1600-40b5-a3de-e7397953b8ba

📥 Commits

Reviewing files that changed from the base of the PR and between fdff6f1 and 576e379.

📒 Files selected for processing (16)
  • internal/api/license_handlers.go
  • internal/api/management_licenses.go
  • internal/api/management_slugs.go
  • internal/api/offline_tokens.go
  • internal/api/server.go
  • internal/api/server_test.go
  • internal/offlinejwt/offlinejwt.go
  • internal/storage/generate_license_test.go
  • internal/storage/license_management.go
  • internal/storage/schema.sql
  • internal/storage/slug_management.go
  • internal/storage/store.go
  • internal/version/version.go
  • ui/src/stores/api.ts
  • ui/src/views/LicensesView.vue
  • ui/src/views/SlugsView.vue

@VisualBean VisualBean merged commit 779ddaa into main Jun 3, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant