Skip to content

Latest commit

 

History

History
185 lines (144 loc) · 9.6 KB

File metadata and controls

185 lines (144 loc) · 9.6 KB

AGENTS.md

Source-of-truth guide for AI agents (Claude Code, Codex, etc.) working in this repository. CLAUDE.md is a thin pointer to this file; keep substantive changes here.

Required Reading Before Writing Cypher

db/schema-simple.json is the authoritative dump of the live Neo4j graph schema — every node label, its properties (with types), every relationship type with its valid start/end label sets. Before writing or reviewing any Cypher query (in *-db-queries.go, a migration, or ad-hoc), consult it to confirm:

  • the exact label spelling and which properties exist on a node,
  • the relationship type name and which labels it can connect (some types are reused across many start/end labels — e.g. WAS_UPDATED_BY, BELONGS_TO_FACILITY),
  • whether a property is String, Long, Double, Boolean, DateTime, or StringArray.

The file is regenerated by running db/schema-simple-query.cypher against the live DB (requires the APOC plugin). Re-dump it after any schema-changing migration.

Build / Run / Test

  • make installgo mod download && go mod verify
  • make swaggerswag init -g server.go then copy docs/swagger.yaml to open-api-specification/panda-api.yaml. Run after any handler / Swagger annotation change.
  • make run — regenerates Swagger then go run server.go
  • make build — regenerates Swagger then builds the binary with -ldflags "-s -w"
  • make testgo test ./...
  • Single test: go test ./services/<pkg> -run TestFunctionName
  • Local stack (API + Neo4j + MinIO): docker-compose -f docker-compose-local.yml up -d --build / ... down
  • Debug build: swag init -g server.go && dlv debug . --build-flags "-tags=dev" (needs dlv)
  • Swagger build tags: -tags=dev|test|prod select swagger_dev.go / swagger_test.go / swagger_prod.go base config

Test DB connection is initialized once via services/testsetup/testsetup.go's init() — it loads .env (project root, two levels up from a service test dir) and opens a shared Neo4j driver/session. There is no automatic per-test cleanup (CleanTestDatabase is intentionally a no-op); tests must not leave durable state or must clean up themselves.

Architecture

REST API gateway for the PANDA database (Go 1.22 + Echo + Neo4j community edition). Vertical-slice service layout designed to allow future microservices extraction.

Boot flow (server.go)

  1. Sets time.Local to Europe/Prague.
  2. Loads config from .env via config/config.go (godotenv autoload).
  3. Wires Echo middlewares: static (Swagger), CORS, request logger, panic recover.
  4. Runs Neo4j migrations (db.MigrateNeo4jMainInstance) using golang-migrate over files in db/neo4j/migrations/ — multi-statement .up.cypher / .down.cypher. Migrations run on every startup; failing migration leaves the SchemaMigration node in dirty: true and blocks subsequent boots until manually resolved.
  5. Creates the shared *neo4j.Driver and the UserStatusValidator (with a TTL cache).
  6. Builds JwtMiddleware(secret, userStatusValidator).
  7. services.InitializeServicesAndMapRoutes(...) — registers every service.
  8. Starts HTTP on :API_PORT, graceful shutdown on SIGINT (10 s timeout).

Service pattern

Each domain lives in services/{name}-service/ with these conventional files:

  • {name}-service.go — service struct, public I{Name}Service interface, business logic
  • {name}-handlers.go — Echo handlers, return echo.HandlerFunc per route
  • {name}-routes.goMap{Name}Routes(e, handlers, jwtMiddleware) registers HTTP routes
  • {name}-db-queries.go — Cypher queries (some services inline them in the service file)
  • models/ (optional) — request/response/db structs

Registration in services/init.go follows the same three-line pattern for every service (New...ServiceNew...HandlersMap...Routes). Initialization order matters: codebook-service depends on catalogue, security, systems, orders, and publications — publications must be initialized before codebook. Adjust order if you add cross-service dependencies.

Auth (JWT + role check)

Custom JWT claims (see helpers/jwt.go): Roles []string, FacilityCode string, Subject string (user UID), Id string (username).

Route shape:

e.GET("/v1/resource", m.Authorization(h.GetResource(), shared.ROLE_SYSTEMS_VIEW), jwtMiddleware)

middlewares.Authorization (in middlewares/auth.go) runs after JWT validation, reads claims via helpers.GetUserFromJWT(c), and sets context values facilityCode, userUID, userName, userRoles. Access granted if the user has any one of the listed roles. Role constants live in shared/roles.go — use them, never string literals.

middlewares.UserStatusValidator caches per-user isEnabled lookups against Neo4j with TTL AUTH_USER_STATUS_CACHE_TTL_SECONDS to avoid hitting the DB on every authenticated request.

Helpers worth knowing

  • helpers/database.goNewNeo4jSession, DatabaseQuery struct, generic readers GetNeo4jSingleRecordAndMapToStruct, GetNeo4jArrayOfNodes, writer WriteNeo4jAndReturnSingleValue. Tag struct fields with neo4j:"prop,<name>" / neo4j:"key,<name>" to drive the experimental CreateOrUpdateNodeQuery reflector.
  • helpers/change_tracking.go — audit-trail helpers for PATCH endpoints. AppendIfChanged(entries, field, ChangeType, old, new) only appends when values differ; codebook values compare by UID field, others via reflect.DeepEqual; typed-nil pointers normalize to JSON null. MarshalChanges(entries) produces the string stored on WAS_UPDATED_BY.changes. Use these for any new mutating endpoint that should record what changed.
  • helpers/field_projection.go — partial-update field projection for PATCH semantics.
  • helpers/echo.go — HTTP error helpers, e.g. BadRequest(message).
  • helpers/jwt.go — token parsing + GetUserFromJWT(c) claims extractor.

HTTP status code conventions

  • POST (create): 201 with object, 400 invalid request (with details), 409 conflict (plain string), 202 async.
  • PUT/PATCH (update): 200 with updated object, 204 no content, 400, 404, 409.
  • DELETE: 200 on success or non-existent entity, 409 conflict, 202 async.
  • GET: 200 with results or empty array; 404 only for specific-entity lookups.
  • Generic: 401 auth, 403 authorization, 405 method not allowed, 500 internal.

Configuration

Copy example.env.env. Key vars:

  • API_JWT_SECRET — JWT signing secret (must be strong in non-local envs)
  • API_PORT — HTTP port (default 50000)
  • BCRYPT_SALT_ROUNDS — password hash cost
  • AUTH_USER_STATUS_CACHE_TTL_SECONDS — UserStatusValidator cache TTL
  • NEO4J_HOST / NEO4J_PORT / NEO4J_USER / NEO4J_PASSWORD / NEO4J_SCHEMA (e.g. bolt://)
  • API_INTEGRATION_B_OKBASE_* — OKBase HR system (employees sync)
  • API_INTEGRATION_B_WOS_STARTER_* — Web of Science (publications import)

Local Neo4j ports (per docker-compose-local.yml mapping):

  • Browser UI: http://localhost:7470
  • Bolt from API/host tools: localhost:7600
  • Bolt from inside Neo4j Browser: neo4j://localhost:7680 (browser-internal mapping, not the default 7687)

Swagger UI (local): http://localhost:50000/swagger/index.html

Database migrations

  • Files in db/neo4j/migrations/ named <timestamp>_<name>.{up,down}.cypher.
  • New migration: db/neo4j/create-new-migration.sh <name> (creates the up/down pair).
  • Manual up/down: db/neo4j/migrate-up.sh / migrate-down.sh.
  • Multi-statement Cypher is supported.
  • Do not put leading // comments at the very top of migration files — the golang-migrate Neo4j driver mis-parses them. Inline // comments inside a statement are fine.
  • Rename refactors that need APOC: use apoc.refactor.rename.* procedures.
  • Dirty migration recovery: connect to Neo4j and manually MATCH (n:SchemaMigration) SET n.dirty = false, n.version = <last-good-version> after fixing the offending Cypher, then restart.

Tests

  • Live next to their packages, _test.go suffix, testify/assert.
  • Shared Neo4j connection from services/testsetup (driver is opened by init()).
  • Single test: go test ./services/<pkg> -run TestFunctionName -v.
  • Many tests assume the local Neo4j stack is up (docker-compose-local.yml) and may rely on migrated schema + seed data — start the stack before running them.

Coding conventions

  • Package names: lowercase, single word.
  • Functions/methods: CamelCase; variables: camelCase.
  • Interfaces prefixed I (e.g. ISecurityService).
  • Imports grouped: standard library, internal (panda/apigateway/...), third-party.
  • Error handling: explicit checks; for HTTP 400 use helpers.BadRequest(...).
  • Logging: github.com/rs/zerolog/log with structured fields.
  • Run gofmt on touched files (tabs for indentation).
  • Add Swagger annotations on every handler; regenerate with make swagger.

Commits & PRs

  • Imperative subject lines; Conventional Commit prefixes are common (feat:, fix:, refactor:, docs:, test:, with optional scope like feat(catalogue): ...).
  • Include issue IDs when applicable (e.g. ELIPANDA-455).
  • Branch from dev (feat/<short-description>), PR back to dev.
  • PR description must cover: what changed, why, how tested, whether Swagger was regenerated, and any migration impact.

Memory / agent-only notes

  • Skills directory: .claude/skills/ is a symlink to .agents/skills/ — edit skills under .agents/, the symlink keeps Claude Code's discovery working.
  • Do not add Claude/AI attribution to commits or PR bodies.
  • Commit per logical step, not one big commit at the end.
  • Never auto-merge PRs.