Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ DIRECT_URL="postgresql://openhosting:openhosting@localhost:5432/openhosting"
CRON_SECRET="change-me"

# Optional: initial admin password used by `npm run db:seed`
# SEED_ADMIN_EMAIL="admin@example.com"
# SEED_ADMIN_PASSWORD="change-me"

# Optional: public origin applied by the seed on a fresh install. After setup,
# manage this under Admin → Settings → Public URL.
# APP_URL="http://localhost:3000"

# ── Ticket attachment storage ───────────────────────────────────────────────
# Default: keep attachment bytes in PostgreSQL. Set to "s3" for AWS S3,
# Cloudflare R2, MinIO, Backblaze B2, or another S3-compatible private bucket.
Expand Down
96 changes: 96 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# OpenHosting repository guidance

## Project summary

OpenHosting is a modular Next.js 16 App Router application for hosting billing,
customer management, support, and infrastructure provisioning. PostgreSQL is
accessed through Prisma 7. Read `README.md` for setup and repository orientation
and `ARCHITECTURE.md` for the core flow before changing cross-domain behavior.

More specific guidance exists in:

- `src/app/AGENTS.md` for pages, layouts, Server Actions, and route handlers.
- `src/lib/extensions/AGENTS.md` for third-party drivers.
- `prisma/AGENTS.md` for schema, migration, and seed work.

## Commands

```bash
npm ci
npm run db:generate
npm test
npm run test:integration
npm run typecheck
npm run build
```

Local development additionally needs PostgreSQL and normally uses:

```bash
cp .env.example .env
npm run db:push
npm run db:seed
npm run dev
```

Unit tests use Vitest. Database integration tests use a disposable PostgreSQL
Testcontainer and apply the checked-in migrations before the test files run;
they therefore require a working Docker daemon. CI requires both suites, Prisma
generation, strict type checking, a production Next.js build, and a Docker
build. Exercise third-party integrations manually against provider sandboxes.

## Architectural boundaries

- `src/app` contains Server Components and route handlers. UI mutations live in
domain modules under `src/lib/actions`.
- `src/lib/services` owns reusable domain workflows. Keep pricing in
`services/orders.ts`, payment integration in `services/payments.ts`, server
integration in `services/provisioning.ts`, resale integration in
`services/resale.ts`, and AI support policy in `services/ai.ts`.
- Keep delegated-spend and idempotency policy in `agent-commerce-policy.ts`,
checkout orchestration in `services/agent-checkout.ts`, protocol adaptation
in `services/acp.ts` / `x402.ts`, and processor calls in gateway drivers.
- `src/lib/billing.ts` owns invoice and recurring-service state transitions. It
must call service abstractions instead of concrete drivers.
- Concrete third-party code belongs under `src/lib/extensions` and is registered
in `src/lib/extensions/registry.ts`.
- All database access goes through `db` from `src/lib/db.ts`.
- Import Prisma types/client code from `@/generated/prisma/client`, never from
`@prisma/client`; generation output is intentionally git-ignored.

## Authentication, authorization, and audit

- Use `requireUser()` for authenticated customer operations and
`requireAdmin(permission)` for staff operations. Perform checks inside every
mutation, even when the corresponding UI is hidden.
- REST handlers use `withApiKey(scope, handler)` and should validate write
payloads with Zod.
- Add `audit()` calls for meaningful auth, admin, billing, account, and service
lifecycle mutations. Never include passwords, tokens, driver credentials, or
full payment details in audit metadata or logs.
- Webhook signature verification belongs in the gateway driver.
- Agent checkout requires a customer-owned `ag_` grant, an exact allowlist,
bounded base-currency spend, expiry, and idempotency. Never weaken one of
those checks in a protocol-specific route.

## UI and settings

- All app routes are deliberately dynamic; the production build must not need
a live database.
- Existing localized UI uses `getT()` and keys from `src/lib/i18n.ts`. English
defines the key union, so add every required locale entry with a new key.
- Theme tokens are registered in `src/lib/themes.ts` and implemented in
`src/app/globals.css`.
- Runtime behavior belongs in the `Setting` table with a safe default in
`src/lib/settings.ts`; deployment-only secrets and connectivity belong in
environment variables.

## Documentation and deployment

- Update the relevant page under `docs/` when operator-visible behavior changes.
- Environment/startup changes may require synchronized edits to `.env.example`,
`Dockerfile`, `docker-entrypoint.sh`, `docker-compose.yml`, `install.sh`, and
`deploy/k8s`.
- Refresh README screenshots with `npx tsx scripts/screenshots.ts` when changing
a pictured UI. The script expects a running app with seeded data.
- Preserve unrelated working-tree changes and do not edit generated artifacts.
53 changes: 39 additions & 14 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architecture

OpenHosting is a single Next.js 15 (App Router) application backed by
OpenHosting is a single Next.js 16 (App Router) application backed by
PostgreSQL through Prisma. Server Components render the UI, Server Actions
handle mutations, and route handlers expose the REST API, webhooks and the
billing cron endpoint.
Expand All @@ -16,18 +16,24 @@ src/
│ ├── admin/ staff panel: billing, catalog, customers, system
│ └── api/
│ ├── v1/ REST API (API-key auth)
│ ├── agent/ delegated catalog and native checkout
│ ├── acp/ versioned Agentic Commerce Protocol adapter
│ ├── mcp/ remote Streamable HTTP MCP transport
│ ├── x402/ exact USDC invoice settlement
│ ├── webhooks/ payment gateway callbacks
│ └── cron/ recurring billing tick
├── lib/
│ ├── auth.ts sessions, password hashing, RBAC guards
│ ├── billing.ts invoice lifecycle + renewal/suspension engine
│ ├── agent-commerce-policy.ts delegated spend/idempotency primitives
│ ├── x402.ts protocol encoding + facilitator boundary
│ ├── cart.ts cookie cart storage
│ ├── mail.ts SMTP delivery + templated emails
│ ├── settings.ts key-value settings with defaults
│ ├── totp.ts RFC-6238 TOTP (no external dependency)
│ ├── actions/ Server Actions, one module per domain
│ ├── services/ orders, payments, provisioning services
│ └── extensions/ gateway & server drivers + registry
│ └── extensions/ gateway, server, resale & AI drivers + registry
└── components/ shared UI (forms, badges, ticket thread)
prisma/ schema, migrations, seed
deploy/k8s/ Kubernetes manifests
Expand All @@ -44,36 +50,55 @@ deploy/k8s/ Kubernetes manifests
that implements the driver interface and one registry entry — no changes to
checkout, billing or the admin UI, which all render driver config forms from
the driver's own `configFields` metadata.
- **Liskov substitution.** Every gateway satisfies `GatewayDriver` and every
provisioning backend satisfies `ServerDriver`; the engine treats them
uniformly and any driver can replace any other.
- **Interface segregation.** Payment and provisioning are separate interfaces;
optional capabilities (webhooks) are optional methods, so a simple offline
gateway like bank transfer implements only what it needs.
- **Liskov substitution.** Gateways, provisioning backends, resale providers,
and AI providers each satisfy their own driver contract; their service
boundary treats implementations of the same contract uniformly.
- **Interface segregation.** Payment, provisioning, resale, and AI use separate
interfaces. Optional capabilities such as gateway webhooks/off-session
charging, resale renewal, and constrained AI output are optional methods.
- **Dependency inversion.** High-level policy depends on abstractions:
`billing.ts` calls `services/provisioning.ts`, and checkout calls
`services/payments.ts`. Only those two service modules resolve concrete
drivers.
`billing.ts` delegates provider work to `services/provisioning.ts` and
`services/resale.ts`, while checkout and webhooks call
`services/payments.ts`. AI support policy lives in `services/ai.ts`; these
service modules, rather than pages or billing policy, resolve drivers.

## Billing lifecycle

```
```text
checkout ──► Order + pending Services + Invoice
payment (gateway webhook / credits / admin) ──► markInvoicePaid()
├─ first payment: Service ACTIVE + driver.create()
└─ renewal: expiresAt += cycle (+ unsuspend if needed)

hourly cron (/api/cron, Bearer CRON_SECRET)
├─ generateRenewalInvoices() N days before expiry
├─ generateRenewalInvoices() N days before expiry (+ metered usage)
├─ autoChargeDueInvoices() supported stored payment method
├─ cancelEndOfTermServices() customer-scheduled cancellation
├─ suspendOverdueServices() grace days after expiry → driver.suspend()
└─ cancelStaleSuspendedServices() after suspension window → driver.terminate()
```

## Agent commerce

```text
customer ──► expiring AgentGrant (products + currencies + spend limits)
agent ──► public catalog ──► native checkout / ACP checkout session
│
├─ serializable spend reservation ──► Order + Invoice
├─ Stripe SPT (fiat) ──► PaymentIntent ──► markInvoicePaid()
└─ x402 v2 (USDC) ──► verify + settle ──► markInvoicePaid()
```

`services/agent-checkout.ts` is the canonical order boundary. ACP adapts its
session lifecycle to that service; x402 adapts signed payment terms to the
normal invoice-payment lifecycle. Protocol routes do not calculate prices or
provision services themselves.

## Security notes

- Sessions are opaque IDs stored server-side with a 14-day TTL; cookies are
`httpOnly` + `SameSite=Lax`.
- Passwords use bcrypt; API keys and one-time tokens are stored as SHA-256
- Passwords use bcrypt; API keys, agent grants, and one-time tokens are stored as SHA-256
hashes only.
- TOTP 2FA uses a ±1 window and constant-time comparison.
- RBAC: users with a `Role` are staff; permissions are checked per admin area
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

OpenHosting is an open-source billing and client-management platform for hosting providers: a single Next.js (App Router) app backed by PostgreSQL through Prisma 7. See ARCHITECTURE.md for the full design; the essentials are below.
OpenHosting is an open-source billing and client-management platform for hosting providers: a single Next.js 16 (App Router) app backed by PostgreSQL through Prisma 7. See ARCHITECTURE.md for the full design; the essentials are below.

## Commands

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Logins after seeding: `admin@example.com` / `admin12345` and
## Good first contributions

- New payment gateways (Mollie, Coinbase Commerce, Razorpay, …) — see
[docs/extensions.md](docs/extensions.md), usually < 100 lines
[Writing an extension](docs/extensions/writing-extensions.md), usually < 100 lines
- New server integrations (Proxmox, VirtFusion, cPanel/WHM, Plesk, CyberPanel)
- Translations / i18n groundwork
- Tests around the billing engine
Loading
Loading