SkillAgentSearch skills...

Cheeblr

Functional Reactive Purescript Deku/Hyrule PoS System for Cannabis Dispensaries

Install / Use

/learn @harryprayiv/Cheeblr
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Cheeblr: Cannabis Dispensary Management System

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.

License

Nix Environment CI & Unit Tests

Documentation

Features

Inventory Management

  • 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 | | Routing | Routing.Duplex + Routing.Hash — hash-based client-side routing | | HTTP | purescript-fetch with Yoga.JSON for serialization | | GraphQL | purescript-graphql-client with AffjaxWebClient — inventory queries via /graphql/inventory | | Money | Data.Finance.MoneyDiscrete USD (integer cents) with formatting | | Async | Effect.Aff with run helper, parSequence_, killFiber for route-driven loading | | Parallelism | Control.Parallel — concurrent data fetching within a single route |

Backend

| Concern | Technology | |---|---| | Language | Haskell | | API | Servant — type-level REST API definitions | | GraphQL | morpheus-graphql — inventory-scoped GraphQL resolver at /graphql/inventory | | Database | postgresql-simple with sql quasiquoter, resource-pool for connection management | | Server | Warp + warp-tls — HTTPS via TLS 1.2+ with mkcert certs in development | | JSON | Aeson (derived + manual instances) | | Auth | Dev-mode X-User-Id header lookup with role-based capabilities |

Infrastructure

| Concern | Technology | |---|---| | Database | PostgreSQL with reservation-based inventory, cascading deletes, parameterized queries | | Dev Environment | Nix flakes — reproducible builds, per-machine dev shells | | Secrets | sops + age — encrypted secrets/cheeblr.yaml, key derived from SSH ed25519 key | | TLS | mkcert for local dev certs; warp-tls for HTTPS on the backend; Vite HTTPS config | | Build (Haskell) | Cabal via haskell.nix / CHaP | | Build (PureScript) | Spago | | Testing | Haskell unit + integration tests; 484 PureScript tests; ephemeral-PostgreSQL integration harness |

Getting Started

Prerequisites

  • Nix with flakes enabled

Development Setup

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) |

Inventory

| Method | Endpoint | Description | |---|---|---| | GET | /inventory | All items with available quantities | | POST | /inventory | Create item (Manager+) | | PUT | /inventory | Update item (Cashier+) | | DELETE | /inventory/:sku | Delete item (Manager+) | | GET | /inventory/available/:sku | Real-time availability (total, reserved, actual) | | POST | /inventory/reserve | Reserve inventory for a transaction | | DELETE | /inventory/release/:id | Release a reservation | | POST | /graphql/inventory | GraphQL endpoint — inventory queries (read-only) |

Transactions

| Method | Endpoint | Description | |---|---|---| | GET | /transaction | List all transactions | | GET | /transaction/:id | Get transaction with items and payments | | POST | /transaction | Create transaction | | PUT | /transaction/:id | Update transaction | | POST | /transaction/void/:id | Void with reason | | POST | /transaction/refund/:id | Create inverse refund transaction | | POST | /transaction/item | Add item (checks availability, creates reservation) | | DELETE | /transaction/item/:id | Remove item (releases reservation) | | POST | /transaction/payment | Add payment | | DELETE | /transaction/payment/:id | Remove payment | | POST | /transaction/finalize/:id | Finalize (commits inventory, completes reservations) | | POST | /transaction/clear/:id | Clear all items/payments, release reservations |

Registers

| Method | Endpoint | Description | |---|---|---| | GET | /register | List registers | | GET | /register/:id | Get register | | POST | /register | Create register | | POST | /register/open/:id | Open with starting cash | | POST | /register/close/:id | Close with counted cash, returns variance |

Architecture

Frontend Architecture

The frontend follows a centralized async loading pattern:

  • Main.purs owns all async data fetching, route matching, and fiber lifecycle management
  • Pages are pure renderers: Poll Status → Nut — no side effects, no launchAff_, no Poll.create
  • Route changes cancel in-flight loading via killFiber on the previous fiber
  • parSequence_ runs multiple loaders in parallel per route
  • Status ADTs per page (Loading | Ready data | Error msg | Degraded partialData) provide type-safe loading states
  • pure Loading <|> poll ensures pages always start with a loading state
  • TxPageDegraded allows the transaction
View on GitHub
GitHub Stars10
CategoryDevelopment
Updated28d ago
Forks2

Languages

PureScript

Security Score

90/100

Audited on Mar 11, 2026

No findings