- 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.
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
.envfile 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: GraphQLObject → GraphQLEnum |
| 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 sortingapp()— Retrieve app configuration including store selection pages, product pages, and account menuquerySuggestions(query_term)— Get search autocomplete suggestionscartProducts(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
-
configcrate for configuration (.env+ environment variables with__separator) -
dotenvyfor.envfile loading -
thiserrorfor error types -
tracing/tracing-subscriber/tracing-actix-webfor logging and request tracing -
async_traitfor async trait implementations -
serde/serde_jsonfor 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: GraphQLObject → GraphQLEnum |
| 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
TracingLoggermiddleware would then actually emit request traces (currently silent)
Scope
- In scope: replace all
println!logging, init subscriber inmain.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