You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A full-stack cannabis dispensary point-of-sale and inventory management system built with PureScript (frontend) and Haskell (backend) on PostgreSQL — emphasizing type safety, functional programming, and reproducible builds via Nix.
Comprehensive Product Tracking: Detailed cannabis product data including strain lineage, THC/CBD content, terpene profiles, species classification, and Leafly integration
Real-Time Inventory Reservations: Items are reserved when added to a transaction cart, preventing overselling during concurrent sessions. Reservations are released on item removal or transaction cancellation, and committed on finalization
Role-Based Access Control: Dev-mode auth system with four roles (Customer, Cashier, Manager, Admin) and 15 granular capabilities governing inventory CRUD, transaction processing, register management, and reporting access
Flexible Sorting & Filtering: Multi-field priority sorting (quantity, category, species) with configurable sort order and optional out-of-stock hiding
Complete CRUD Operations: Create, read, update, and delete inventory items with full strain lineage data
GraphQL Inventory API: Inventory queries available via /graphql/inventory using morpheus-graphql (backend) and purescript-graphql-client (frontend), scoped to read-only inventory access
Point-of-Sale System
Full Transaction Lifecycle: Create → add items (with reservation) → add payments → finalize (commits inventory) or clear (releases reservations)
Parallel Data Loading: The POS page loads inventory, initializes the register, and starts a transaction concurrently using the frontend's parSequence_ pattern; degrades gracefully to TxPageDegraded state on partial load failure
Multiple Payment Methods: Cash, credit, debit, ACH, gift card, stored value, mixed, and custom payment types with change calculation
Tax Management: Per-item tax records with category tracking (regular sales, excise, cannabis, local, medical)
Discount Support: Percentage-based, fixed amount, BOGO, and custom discount types with approval tracking
Automatic Total Recalculation: Server-side recalculation of subtotals, taxes, discounts, and totals on item/payment changes
Financial Operations
Cash Register Management: Open registers with starting cash, close with counted cash and automatic variance calculation
Register Persistence: Register IDs stored in localStorage, auto-recovered on page load via get-or-create pattern
Transaction Modifications: Void (marks existing transaction) and refund (creates inverse transaction with negated amounts) operations with reason tracking
Payment Status Tracking: Transaction status auto-updates based on payment coverage (payments ≥ total → Completed)
Compliance Infrastructure
Customer Verification Types: Age verification, medical card, ID scan, visual inspection, patient registration, purchase limit check
Compliance Records: Per-transaction compliance tracking with verification status, state reporting status, and reference IDs
Reporting Stubs: Compliance and daily financial report endpoints defined with types — implementation pending
Technology Stack
Frontend
Concern
Technology
Language
PureScript — strongly-typed FP compiling to JavaScript
UI
Deku — declarative, hooks-based rendering with Nut as the renderable type
State
FRP.Poll — reactive streams with create/push for mutable cells
git clone https://github.com/harryprayiv/cheeblr.git
cd cheeblr
nix develop
# First-time: set up secrets and TLS
sops-init-key # derive age key from ~/.ssh/id_ed25519
sops-bootstrap # create secrets/cheeblr.yaml with a random DB password
tls-setup # generate mkcert dev certs for localhost
tls-sops-update # encrypt certs into secrets/cheeblr.yaml
sops-status # verify everything is wired up# Start everything
pg-start
deploy # tmux session: backend (HTTPS :8080) + frontend (HTTPS :5173) + pg-stats
See Nix Development Environment for the full command reference, individual service scripts (backend-start, frontend-start, etc.), and the test suite.
API Overview
Session
Method
Endpoint
Description
GET
/session
Current user capabilities (separated from inventory payload)
TLS everywhere: backend runs warp-tls; frontend Vite dev server configured for HTTPS; all service scripts inject USE_TLS, TLS_CERT_FILE, TLS_KEY_FILE from sops
Parameterized queries throughout — no string interpolation in SQL
Type safety across the full stack — shared domain types between PureScript and Haskell enforce JSON contract at compile time (contract tests catch serialization divergence)
Role-based capabilities — 15 granular permissions mapped to 4 roles, enforced on inventory writes
Input validation — frontend (ValidationRule combinators) and backend (type-level constraints via Servant)
Secrets management — database password and TLS cert/key stored in sops-encrypted secrets/cheeblr.yaml; never in plaintext on disk
Current limitation: Authentication is dev-mode only (X-User-Id header with fixed users). See Security Recommendations for the planned upgrade path to libsodium public-key challenge-response.
Testing
test-unit # Haskell unit tests + 484 PureScript tests (no services needed)
test-integration # ephemeral PostgreSQL + backend on :18080, HTTP integration suite
test-integration-tls # same as above with TLS; validates cert SAN and plain-HTTP rejection
test-suite # all three phases in sequence
test-smoke # hit live backend on :8080, check endpoint and JSON contract health
Integration tests spin up and tear down their own isolated PostgreSQL instance so they can run independently of pg-start.
Development Status
Implemented
Full inventory CRUD with strain lineage
Inventory reservation system (reserve on cart add, release on remove, commit on finalize)