Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/gitrus/digikeeper-log/internal/httpapi"
apicmd "github.com/gitrus/digikeeper-log/internal/httpapi/command"
apiqry "github.com/gitrus/digikeeper-log/internal/httpapi/query"
apireg "github.com/gitrus/digikeeper-log/internal/httpapi/registry"
apisreg "github.com/gitrus/digikeeper-log/internal/httpapi/schemaregistry"
"github.com/gitrus/digikeeper-log/internal/infrastructure/candidatestore"
store "github.com/gitrus/digikeeper-log/internal/infrastructure/commandstore"
"github.com/gitrus/digikeeper-log/internal/infrastructure/index"
Expand Down Expand Up @@ -82,16 +82,20 @@ func run() error {

// Services
cmdSvc := command.NewService(logStore, srcRepo, logger)
candidateSvc := domainCandidate.NewService(candidateStore, logStore, logger)
compactionSvc := domainCompaction.NewService(logStore, candidateStore, idx, logger)
candidateSvc := domainCandidate.NewService(
candidateStore, logStore, logger,
)
compactionSvc := domainCompaction.NewService(
logStore, candidateStore, idx, logger,
)
qrySvc := query.NewService(qryStore, qryStore, logger)

// Handlers
cmdHandler := apicmd.NewHandler(cmdSvc, srcRepo.ResolveName)
candidateHandler := apicmd.NewCandidateHandler(candidateSvc)
compactionHandler := apicmd.NewCompactionHandler(compactionSvc)
qryHandler := apiqry.NewHandler(qrySvc, srcRepo.ResolveName)
regHandler, err := apireg.NewHandler()
sregHandler, err := apisreg.NewHandler()
if err != nil {
return fmt.Errorf("init registry: %w", err)
}
Expand Down Expand Up @@ -149,14 +153,14 @@ func run() error {
Path: "/v1/registry",
Summary: "List all entry type schemas",
DefaultStatus: http.StatusOK,
}, regHandler.ListSchemas)
}, sregHandler.ListSchemas)
huma.Register(api, huma.Operation{
OperationID: "get-schema",
Method: http.MethodGet,
Path: "/v1/registry/{type}",
Summary: "Get schema for an entry type",
DefaultStatus: http.StatusOK,
}, regHandler.GetSchema)
}, sregHandler.GetSchema)

mux.HandleFunc("GET /healthz", healthz.Handle)
mux.Handle("/debug/", http.DefaultServeMux)
Expand Down
4 changes: 2 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Dependencies flow inward. Interfaces defined at the usage-model-level.
## Handler Segregation

Handlers under `internal/httpapi/` mirror the CQS split so that read and write paths evolve independently — different validation rules, status codes, and future middleware (e.g. rate limiting writes only).
`httpapi/command` owns mutation concerns; `httpapi/query` owns read concerns; `httpapi/registry` is stateless schema discovery with no domain coupling.
`httpapi/command` owns mutation concerns; `httpapi/query` owns read concerns; `httpapi/schemaregistry` is stateless schema discovery with no domain coupling.
Shared utilities (`response.go`, `errors.go`, `middleware.go`) are kept at the `httpapi/` root to avoid duplication without blurring the command/query boundary.

## Infrastructure Split
Expand All @@ -61,7 +61,7 @@ The API follows the main guidelines of [JSON:API](https://jsonapi.org/) specific
Before making API design decisions, consult the spec and its addendums first.

## See Also
- [Registry Handlers](REGISTRY_HANDLERS.md)
- [Schema Registry Handlers](SCHEMA_REGISTRY_HANDLERS.md)
- [Candidate Compaction](CANDIDATE_COMPACTION.md)

## Trade-offs
Expand Down
12 changes: 6 additions & 6 deletions docs/REGISTRY_HANDLERS.md → docs/SCHEMA_REGISTRY_HANDLERS.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Registry Handlers
# Schema Registry Handlers

## Description

Registry handlers expose supported log entry schemas over HTTP.
They live in `internal/httpapi/registry` and are wired from `cmd/server`.
Schema registry handlers expose supported log entry schemas over HTTP.
They live in `internal/httpapi/schemaregistry` and are wired from `cmd/server`.

Current endpoints:
- `GET /v1/registry` returns all known entry schemas.
- `GET /v1/registry/{type}` returns one schema by entry type.

Schemas are JSON files in `internal/httpapi/registry/schemas`.
Schemas are JSON files in `internal/httpapi/schemaregistry/schemas`.
The handler embeds them with `go:embed`, loads them at startup, and keeps each schema as `json.RawMessage`.
The filename without `.json` becomes the public entry type name.

Example:
- `schemas/note.json` becomes registry type `note`.
- `schemas/note.json` becomes schema registry type `note`.

## Why It Exists

Expand All @@ -24,7 +24,7 @@ It cna be used by agentic client as well.

## Boundaries

The registry is read-only application metadata, not user data.
The schema registry is read-only application metadata, not user data.
Schema changes should go through code review and deployment.

The handler stays in `internal/httpapi` because it has no business workflow or mutable storage.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package registry
package schemaregistry

import (
"context"
Expand Down Expand Up @@ -27,7 +27,7 @@ type Handler struct {
func NewHandler() (*Handler, error) {
entries, err := schemasFS.ReadDir("schemas")
if err != nil {
return nil, fmt.Errorf("registry: read embedded schemas: %w", err)
return nil, fmt.Errorf("schemaregistry: read embedded schemas: %w", err)
}

schemas := make(map[string]json.RawMessage, len(entries))
Expand All @@ -38,7 +38,7 @@ func NewHandler() (*Handler, error) {
}
data, err := schemasFS.ReadFile("schemas/" + e.Name())
if err != nil {
return nil, fmt.Errorf("registry: read %s: %w", e.Name(), err)
return nil, fmt.Errorf("schemaregistry: read %s: %w", e.Name(), err)
}
name := strings.TrimSuffix(e.Name(), ".json")
schemas[name] = json.RawMessage(data)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package registry_test
package schemaregistry_test

import (
"encoding/json"
Expand All @@ -12,13 +12,13 @@ import (
"github.com/stretchr/testify/require"

"github.com/gitrus/digikeeper-log/internal/httpapi"
apireg "github.com/gitrus/digikeeper-log/internal/httpapi/registry"
apisreg "github.com/gitrus/digikeeper-log/internal/httpapi/schemaregistry"
)

func setupRegistryServer(t *testing.T) *httptest.Server {
t.Helper()

h, err := apireg.NewHandler()
h, err := apisreg.NewHandler()
require.NoError(t, err)

mux := http.NewServeMux()
Expand Down
14 changes: 7 additions & 7 deletions tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"github.com/gitrus/digikeeper-log/internal/httpapi"
apicmd "github.com/gitrus/digikeeper-log/internal/httpapi/command"
apiqry "github.com/gitrus/digikeeper-log/internal/httpapi/query"
apireg "github.com/gitrus/digikeeper-log/internal/httpapi/registry"
apisreg "github.com/gitrus/digikeeper-log/internal/httpapi/schemaregistry"
"github.com/gitrus/digikeeper-log/internal/infrastructure/candidatestore"
store "github.com/gitrus/digikeeper-log/internal/infrastructure/commandstore"
"github.com/gitrus/digikeeper-log/internal/infrastructure/index"
Expand Down Expand Up @@ -131,8 +131,8 @@ func setupTestServer(t *testing.T) *httptest.Server {
candidateHandler := apicmd.NewCandidateHandler(candidateSvc)
compactionHandler := apicmd.NewCompactionHandler(compactionSvc)
qryHandler := apiqry.NewHandler(qrySvc, srcRepo.ResolveName)
regHandler, err := apireg.NewHandler()
require.NoError(t, err, "init registry")
sregHandler, err := apisreg.NewHandler()
require.NoError(t, err, "init schema registry")

mux := http.NewServeMux()
api := humago.New(mux, httpapi.NewHumaConfig("Digikeeper Log", "1.0.0"))
Expand Down Expand Up @@ -187,14 +187,14 @@ func setupTestServer(t *testing.T) *httptest.Server {
Path: "/v1/registry",
Summary: "List all entry type schemas",
DefaultStatus: http.StatusOK,
}, regHandler.ListSchemas)
}, sregHandler.ListSchemas)
huma.Register(api, huma.Operation{
OperationID: "get-schema",
Method: http.MethodGet,
Path: "/v1/registry/{type}",
Summary: "Get schema for an entry type",
DefaultStatus: http.StatusOK,
}, regHandler.GetSchema)
}, sregHandler.GetSchema)

sloghttp.RequestIDHeaderKey = "X-Request-ID"
handler := sloghttp.NewWithConfig(logger, sloghttp.Config{
Expand Down Expand Up @@ -498,7 +498,7 @@ func TestAppendGeneratesRequestIDWhenMissing(t *testing.T) {
assert.NotEmpty(t, got.Data.Attributes.RequestID)
}

func TestRegistryListSchemas(t *testing.T) {
func TestSchemaRegistryListSchemas(t *testing.T) {
srv := setupTestServer(t)

resp := getURL(t, srv.URL+"/v1/registry")
Expand All @@ -517,7 +517,7 @@ func TestRegistryListSchemas(t *testing.T) {
assert.Equal(t, "note", got.Schemas[0].Type)
}

func TestRegistryGetSchema(t *testing.T) {
func TestSchemaRegistryGetSchema(t *testing.T) {
srv := setupTestServer(t)

resp := getURL(t, srv.URL+"/v1/registry/note")
Expand Down