server/AGENTS.md
David Kong 96b6bc1e78 docs: harmonize AGENTS.md, DOMAIN.md, and README.md — fix inconsistencies across all three documents
- Expand BDP section in AGENTS.md from 4 entities to 18+ (Invoices, Inventory, Employees, Business Units, Locations, Suppliers, Manufacturers, Returns/RMA, Templates, Tasks & Task Lists, Service Jobs, Status Definitions, Sequences, Financial Accounts)
- Add Rust edition 2024 and missing crates to AGENTS.md Technical Implementation (serde, config, dotenvy, thiserror, tracing)
- Add Coding Conventions table for BDP section in AGENTS.md
- Update Database Configuration with specific variables (DATABASE_URL, server__host, server__port)
- Add Domain Concepts section referencing DOMAIN.md in AGENTS.md (_underscore fields, reference resolution pattern, AUD default currency, BU allocation)
- Add Domain Concepts section to README.md filling gaps
- Peer harmonization approach: no single source of truth; all three docs are peers with cross-references

See HARMONIZATION_DIFF.md for before/after diff summary.
2026-06-26 21:11:17 +10:00

16 KiB

Agents

This repository is a Business Data Platform (BDP) that provides a GraphQL API. The API allows AI agents to manage business entities including orders, customers, sales quotes, and products through GraphQL operations.

For comprehensive domain terminology, entity relationships, enums, and business concepts, see DOMAIN.md.

Available Operations

AI agents can perform the following operations via GraphQL:

Orders

  • Create new orders (supports MASTER/SUBORDER aggregation)
  • Update existing orders
  • Delete orders
  • Retrieve order information

Customers

  • Create new customer records
  • Update customer information
  • Delete customer records
  • Retrieve customer details

Sales Quotes

  • Create sales quotes
  • Update quote information
  • Delete quotes
  • Retrieve quote data

Products

  • Create product listings
  • Update product information
  • Delete products
  • Retrieve product details

Invoices

  • Create billing documents (sales invoices, RMA returns, employee invoices)
  • Update invoice information
  • Delete invoices
  • Retrieve invoice data with payment tracking and tax breakdowns

Inventory

  • Manage individual stock units tracked by serial number or batch
  • Support scan-in/out operations and manufacturing tracking
  • Retrieve inventory records linked to products and supplier invoices

Employees

  • Create staff records with payroll, schedule, entitlements, PAYG tax settings
  • Update employee information including authentication links
  • Delete employee records
  • Retrieve employee details

Business Units

  • Create top-level organizational entities grouping employees, locations, customers, and financial accounts
  • Define currency, locale (en-AU default), and tax settings (GST or No GST)
  • Update business unit configuration
  • Delete business units
  • Retrieve business unit records

Locations

  • Create physical sites associated with business units (warehouses, offices) with operating hours and addresses
  • Update location information
  • Delete locations
  • Retrieve location data

Suppliers

  • Create external vendor/service provider records with credit terms and registration
  • Update supplier category profiles and credit details
  • Delete suppliers
  • Retrieve supplier information

Manufacturers

  • Create entities that produce goods; referenced by products for identification
  • Update manufacturer information
  • Delete manufacturers
  • Retrieve manufacturer records

Returns (RMA)

  • Create return merchandise authorization tracking: fault reporting, replacements, resolution actions
  • Manage customer and supplier returns with completion date tracking
  • Delete RMA records
  • Retrieve RMA data including inventory references

Templates

  • Create reusable blueprints for orders, quotes, emails, leads, tickets, and articles
  • Update template content (can be scoped: global or personal/owned by a specific Customer)
  • Delete templates
  • Retrieve template records

Tasks & Task Lists

  • Create work items with scheduling, assignment, progress tracking, status history
  • Manage work done entries and task list organization
  • Delete tasks
  • Retrieve task records

Service Jobs

  • Create paid or warranty service performed for customers; includes work tracking and scheduling
  • Update service job details
  • Delete service jobs
  • Retrieve service job data

Status Definitions

  • Create central registry of reusable status states with visual styling and allowed actions
  • Update status definitions (styling, permitted transitions)
  • Delete status definitions
  • Retrieve status records

Sequences

  • Manage shared counters for auto-generating unique identifiers (order numbers, invoice numbers)
  • Each sequence belongs to a model (e.g., Order, Invoice) and increments the corresponding field
  • Default starting count is 200000
  • Update sequence configuration
  • Delete sequences
  • Retrieve sequence records

Financial Accounts

  • Create bank accounts or payment methods used by the business for transactions
  • Update account information
  • Delete financial accounts
  • Retrieve financial account data

Technical Implementation

The API is built using:

  • Rust programming language (edition 2024)
  • Juniper GraphQL framework (juniper, juniper_actix)
  • Actix-web HTTP server (actix-web, actix-files, actix-cors)
  • Tokio async runtime
  • SQLx for database operations (via sqlx-postgres) connecting to PostgreSQL
  • Serde / serde_json for serialization
  • Config crate for configuration (.env + environment variables with __ separator)
  • Dotenvy for .env file loading
  • Thiserror for error types
  • Tracing, tracing-subscriber, tracing-actix-web for logging and request tracing

Database Configuration

Database connection parameters are configured via environment variables using the __ separator convention:

Variable Purpose
DATABASE_URL Full PostgreSQL connection string (e.g. postgres://user:pass@host:port/dbname)
server__host Server bind host address
server__port Server bind port number

Create a .env file with your configuration values before running the server. The config crate loads defaults → .env → environment variables in that priority order.

Coding Conventions

Convention Detail
Indentation 4 spaces for Rust; 2 spaces for YAML/JSON/TOML
File naming One struct/type per file. File name matches the type (e.g. product_card.rs for ProductCard)
Module visibility pub(crate) for internal-only modules; pub for public-facing ones (graphql/models, canonical)
Struct naming PascalCase (e.g. ProductCard, MeilisearchRepository, AppConfig)
Enum naming PascalCase struct names, uppercase variants (e.g. ProductTagStyle::STANDARD)
Field naming snake_case (e.g. product_id, price_as_cents, invoice_date)
Derive macros GraphQL objects: #[derive(Debug, Deserialize, Serialize, GraphQLObject)]; Enums: GraphQLObjectGraphQLEnum
Error handling Box<dyn Error> in repository layer; context errors propagated via FieldResult
Async traits Repository interfaces use #[async_trait] for async method definitions
Configuration loading Use config crate builder: defaults → .env (via dotenvy) → environment variables (__ separator). E.g. server__host maps to server.host
Repository pattern Abstract interface (trait) in infra/repositories/traits/, concrete impl in infra/repositories/. Client wrapper (e.g. MeilisearchClient) is a separate struct that wraps reqwest::Client with auth headers
Context object Single AppContext struct passed to all GraphQL resolvers; holds AppConfig + repository clones

Domain Concepts

When working with the BDP, agents should be aware of key domain concepts defined in DOMAIN.md:

  • _underscore fields — System-managed metadata prefixed with underscore (_id, _entered.by, _modified.ts, _total, _active). Never set by agents directly.
  • Reference resolution pattern — All entity references follow reference._id → query the referenced entity. See DOMAIN.md for full reference resolution guide.
  • Default currency — AUD (Australian Dollar) across the platform.
  • Business Unit & Location allocation — Every participating entity must be assigned to a Business Unit and often a Location (required on save).
  • Master/Suborder aggregation — Orders can be grouped hierarchically; Master orders aggregate totals from linked Suborders.
  • Classification/Profile pattern — Customers and Products use extendable schema-driven classification via classification.name, classification.profile._id, classification.profile.fields.

Authentication and Authorization

This API does not specify authentication or authorization mechanisms in the requirements, but AI agents would typically require appropriate access tokens or credentials to perform operations.


Shoppy BFF (~/Projects/shoppy/bff)

This repository is a Backend For Frontend (BFF) GraphQL service for the Shoppy mobile app. It aggregates data from external search services and exposes it via a single GraphQL endpoint, serving as an API gateway between the frontend apps (iOS/Android) and backend infrastructure.

Available Operations

AI agents can perform the following operations via its /graphql endpoint:

Queries

  • productList(listType, argument, page_number?, facets?, sort_by?) — Search or browse products with faceted filtering and sorting
  • app() — Retrieve app configuration including store selection pages, product pages, and account menu
  • querySuggestions(query_term) — Get search autocomplete suggestions
  • cartProducts(input) — Resolve cart items by product IDs (returns product cards or not-found placeholders)
  • smartSwaps(input) — Fetch "swap and save" alternative products for given cart items

Mutations

  • submitFeedback(feedback) — Submit user feedback (stub implementation, always returns true)

Technical Implementation

The service is built using:

  • Rust programming language (edition 2024)

  • Juniper GraphQL framework (juniper, juniper_actix)

  • Actix-web HTTP server (actix-web, actix-files, actix-cors)

  • Tokio async runtime

  • config crate for configuration (.env + environment variables with __ separator)

  • dotenvy for .env file loading

  • thiserror for error types

  • tracing / tracing-subscriber / tracing-actix-web for logging and request tracing

  • async_trait for async trait implementations

  • serde / serde_json for serialization

Folder Structure

src/
├── main.rs                 # Entry point: server boot, schema registration, middleware chain
├── mod.rs                  # Root module declarations (pub(crate) vs pub)
├── app_config.rs           # Configuration struct + loading logic (config crate builder pattern)
│
├── graphql/                # GraphQL layer — public-facing schema & resolvers
│   ├── schema.rs           # Schema type alias + factory function
│   ├── mod.rs              
│   ├── models/             # GraphQL output types (PascalCase structs/enums, all #[derive(GraphQLObject)])
│   │   └── mod.rs          # One file per model struct/type
│   ├── queries/
│   │   ├── query_root.rs   # QueryRoot struct + #[juniper::graphql_object] impl
│   │   ├── helpers.rs      # Shared resolver helpers (create_product_list, facet utilities)
│   │   └── resolvers/      # Per-domain resolver modules (app_content_resolvers, etc.)
│   └── mutations/
│       └── mutation_root.rs # MutationRoot struct + #[juniper::graphql_object] impl
│
├── infra/                  # Infrastructure layer — external service integrations
│   ├── mod.rs              
│   ├── models/             # Internal domain types (infrastructure response structs, helpers)
│   │   └── helpers/        # Helper modules (category_helpers, image_helpers, pricing_helpers, stores_helpers)
│   └── repositories/
│       ├── meilisearch_client.rs  # reqwest Client wrapper with auth headers
│       ├── meilisearch_repository.rs  # Main repository implementation
│       ├── meilisearch_helpers.rs   # Query-building helpers (apply_facets, create_filters)
│       ├── traits/          # Abstract repository interface
│       └── mod.rs
│
├── canonical/              # Canonical / shared domain models (clean types without infra coupling)
│   └── models/
│       ├── search.rs        # SortOption, SearchFacet — pure domain structs
│       └── mod.rs
│
└── middleware/             # HTTP middleware
    ├── shoppy_api_key.rs    # API-key validation (checks shoppy-api-key header against config)
    └── mod.rs

Coding Conventions

Convention Detail
Indentation 4 spaces for Rust; 2 spaces for YAML/JSON/TOML; tabs for Makefiles
File naming One struct/type per file. File name matches the type (e.g. product_card.rs for ProductCard)
Module visibility pub(crate) for internal-only modules; pub for public-facing ones (graphql/models, canonical)
Struct naming PascalCase (e.g. ProductCard, MeilisearchRepository, AppConfig)
Enum naming PascalCase struct names, uppercase variants (e.g. ProductTagStyle::STANDARD)
Field naming snake_case (e.g. product_id, price_as_cents, meilisearch_url)
Derive macros GraphQL objects: #[derive(Debug, Deserialize, Serialize, GraphQLObject)]; Enums: GraphQLObjectGraphQLEnum
Error handling Box<dyn Error> in repository layer; context errors propagated via FieldResult
Async traits Repository interfaces use #[async_trait] for async method definitions
Configuration loading Use config crate builder: defaults → .env (via dotenvy) → environment variables (__ separator). E.g. server__host maps to server.host
Logging (current) Unstructured println!("[INFO] -- ...") across ~40 call sites; tracing-actix-web::TracingLogger wired as middleware but silent due to missing subscriber init. Requirement: migrate to structured tracing with EnvFilter, JSON output in production, compact output locally, and activate request-level tracing
Repository pattern Abstract interface (trait) in infra/repositories/traits/, concrete impl in infra/repositories/. Client wrapper (e.g. MeilisearchClient) is a separate struct that wraps reqwest::Client with auth headers
Context object Single AppContext struct passed to all GraphQL resolvers; holds AppConfig + repository clones
Docker build Multi-stage using cargo-chef for dependency caching: chef → planner (recipe) → builder (cook + build) → runtime (debian slim, binary only)

Configuration

Environment variables use the __ separator convention:

  • server__host, server__port — server bind settings

  • proxy__http, proxy__https — proxy settings (optional)

  • auth__sx_ios_api_key, auth__android_sx_api_key — API keys for iOS/Android app authentication

Authentication

The BFF validates a shoppy-api-key header against the configured auth.sx_ios_api_key or auth.android_sx_api_key. The /graphql endpoint and /healthz are exempt from this check. GraphQL itself does not require auth — only non-GraphQL routes do.

Logging Requirement

Current state

All logging uses raw println!("[INFO] -- ...") with no timestamps, log levels, or field context (~40 call sites). This makes logs unparseable and hard to filter in production. The project already has the right crates (tracing, tracing-subscriber, tracing-actix-web) but they are unused.

Required approach: layered structured tracing

Layer Purpose
EnvFilter Runtime log level control via RUST_LOG env var (e.g. shoppy_bff=debug, reqwest=warn)
Fmt layer — JSON in prod, Compact in dev Structured logs for production aggregation; readable compact output locally
tracing-actix-web::TracingLogger Middleware auto-logs every HTTP request (method, path, status, latency)

Implementation notes

  • Replace println!("[INFO] -- ...")tracing::info!(...) with structured fields (e.g., tracing::error!(status = ?response_status, "Error getting products by search"))
  • Initialize subscriber in main.rs:
    let fmt_layer = tracing_subscriber::fmt::layer().with_target(false);
    let filter = EnvFilter::try_from_default_env()
        .or_else(|_| EnvFilter::try_new("info")).unwrap();
    #[cfg(debug_assertions)]
    let fmt_layer = fmt_layer.pretty();
    #[cfg(not(debug_assertions))]
    let fmt_layer = fmt_layer.json();
    tracing_subscriber::registry()
        .with(filter)
        .with(fmt_layer)
        .init();
    
  • The TracingLogger middleware would then actually emit request traces (currently silent)

Scope

  • In scope: replace all println! logging, init subscriber in main.rs, activate TracingLogger output
  • Out of scope: OpenTelemetry integration, log crate bridge, multi-layer stdout/JSON split — these can be added later if log aggregation becomes a requirement