diff --git a/README.md b/README.md index 94b64fc..3f33322 100755 --- a/README.md +++ b/README.md @@ -1,53 +1,55 @@ # Gresbase -> A self-hosted backend platform — PostgreSQL-backed collections, authentication, realtime, and file storage in a single Go binary. +> A self-hosted backend platform with PostgreSQL-backed collections, authentication, realtime, and file storage in a single Go binary. -> **Status: 1.0 — production-ready core.** +> **Status 1.0, production-ready core.** > The core (auth, collections, records, locked-by-default rules, realtime, dashboard, file storage) is production-ready and secure by default. > Embedded single-binary PostgreSQL is intended for development and small deployments; run an external PostgreSQL in production. > See [Status](#project-status) for an honest assessment. ## Secure by default -- **Collections are locked on creation** — every access rule starts as `null` (superusers only). You explicitly open what should be public. +- **Collections are locked on creation**. Every access rule starts as `null` (superusers only). You explicitly open what should be public. - Access rules are enforced on **every read path**: list, view, search, batch, file downloads, and realtime event delivery. - Production refuses to start with a missing, generated, or weak `JWT_SECRET`. - Auth endpoints (admin and end-user) are rate limited by IP. - Dashboard auth uses httpOnly cookies with CSRF protection. -## Postgres-native superpowers — one process, one database +![Screen recording: a new collection is locked, an anonymous request to its list endpoint returns 403 Access denied, then after the list rule is set to Public the same request returns the records](docs/media/locked-by-default.gif) + +## Postgres-native superpowers: one process, one database The governing principle: **PostgreSQL is the only infrastructure.** Anything a larger platform does with a sidecar service, Gresbase does with a Postgres feature. -- **Vector search (pgvector)** — add a `vector` field, store embeddings, run rule-aware similarity search. The AI/RAG use-case SQLite-based tools can't reach. → `POST /api/v1/records/{c}/search-vector` -- **RLS-grade rules without the footguns** — locked-by-default app-layer rules with a preset catalog and a **rule simulator** ("would user X pass this rule against this record?") so policies are testable before they ship. -- **Real Postgres RLS, generated** — compile your collection rules into `CREATE POLICY` statements for a restricted role, so direct database connections are guarded too. → `GET /api/v1/rls/script`, `POST /api/v1/rls/apply` -- **Webhooks without pg_net** — HMAC-SHA256-signed event forwarding (Stripe-style `t=,v1=` signatures) with retries and a delivery log, delivered by in-process workers. → `/api/v1/webhooks` -- **SQL console + schema introspection** — superuser SQL editor (read-only by default) and a structured schema API, all in-process. No separate database-metadata service to run. → `POST /api/v1/sql`, `GET /api/v1/sql/schema` -- **Typed SDK from your schema** — `gresbase types` or `GET /api/v1/types.ts` generates a typed TypeScript client straight from your live schema. No code-generation service to run. -- **Horizontal realtime** — set `realtime_multi_node: true` and record events travel between app nodes over Postgres `LISTEN/NOTIFY`. No Redis, no broker. -- **Prometheus metrics in-process** — `GET /metrics` text exposition, optional bearer token. No log-shipping sidecars. -- **REST aggregations** — `count`, `sum`, `avg`, `min`, `max` with `groupBy`, filtered and **list-rule enforced**, computed in SQL so you never ship rows to the client just to count them. → `GET /api/v1/records/{c}/aggregate?aggregate=count,sum:amount&groupBy=status` -- **Rule-enforced relation expansion** — forward (`?expand=author`), back-relations (`?expand=comments_via_post`), and nested paths (`comments_via_post.user`, up to 6 levels). Every level honors the target collection's rules, so a public collection can never leak a locked one through expand. -- **Anonymous sign-in** — `POST /api/v1/collections/{c}/auth/auth-with-anonymous` mints a throwaway user for try-before-signup flows. Off by default per collection; gate rules with `@request.auth.anonymous = false`. -- **Passkeys (WebAuthn)** — phishing-resistant, passwordless sign-in for auth collections, in-process via go-webauthn. Off by default per collection (`allowPasskeys`); discoverable credentials, no email enumeration, SDK helpers included. → `POST /api/v1/collections/{c}/auth/passkey/login-begin` -- **WAL change capture** — opt-in logical-replication consumer (`realtime_wal_enabled`) so rows changed by *direct SQL* — psql, the SQL console, the generated RLS role — reach realtime subscribers too, rule-checked like everything else. Captured in-process via `pglogrepl`; no separate change-data-capture service. -- **Resumable uploads (TUS)** — `POST /api/v1/files/tus/` attaches large files to records over flaky connections with full rule + field-constraint enforcement. No upload sidecar to run. -- **Schema migrations as files** — dev-mode collection changes are snapshotted to `./gb_migrations/*.json` and replayed on boot, so schemas live in git and deploy reproducibly (`gresbase migrations snapshot|list|apply`). -- **Editable email templates** — every transactional email customizable from the dashboard, validated at save time, with built-in defaults as a can't-break fallback. -- **Realtime broadcast + presence** — client-to-client channel messages (`POST /api/v1/realtime/broadcast`, auth required) and per-channel presence with join/leave events and member state. Travels across nodes over `LISTEN/NOTIFY` in multi-node mode. -- **On-the-fly image transforms** — `?thumb=400x300f&format=jpeg&quality=80` on file URLs, generated in-process and cached. No image-proxy sidecar to run. -- **File-based JS hooks with hot reload** — drop `*.js` files in `./gb_hooks` to handle record/auth events; edits reload live. No Deno runtime, no cold starts. Scaffold with `gresbase hooks init` (includes `types.d.ts` for editor autocomplete). -- **Read-replica routing** — set `DATABASE_REPLICA_URL` and record lists, aggregations, and relation expansion route to a PostgreSQL read replica; writes and rule-feeding reads stay on the primary. The scale lever a single-writer SQLite backend structurally cannot offer. -- **Embed as a Go framework** — write hooks in real Go and add custom routes; see [`backend/examples/embed`](backend/examples/embed). +- **Vector search (pgvector)**. Add a `vector` field, store embeddings, run rule-aware similarity search. The AI/RAG use-case SQLite-based tools can't reach. → `POST /api/v1/records/{c}/search-vector` +- **RLS-grade rules without the footguns**. Locked-by-default app-layer rules with a preset catalog and a **rule simulator** ("would user X pass this rule against this record?") so policies are testable before they ship. +- **Real Postgres RLS, generated**. Compile your collection rules into `CREATE POLICY` statements for a restricted role, so direct database connections are guarded too. → `GET /api/v1/rls/script`, `POST /api/v1/rls/apply` +- **Webhooks without pg_net**. HMAC-SHA256-signed event forwarding (Stripe-style `t=,v1=` signatures) with retries and a delivery log, delivered by in-process workers. → `/api/v1/webhooks` +- **SQL console + schema introspection**. A superuser SQL editor (read-only by default) and a structured schema API, all in-process. No separate database-metadata service to run. → `POST /api/v1/sql`, `GET /api/v1/sql/schema` +- **Typed SDK from your schema**. `gresbase types` or `GET /api/v1/types.ts` generates a typed TypeScript client straight from your live schema. No code-generation service to run. +- **Horizontal realtime**. Set `realtime_multi_node: true` and record events travel between app nodes over Postgres `LISTEN/NOTIFY`. No Redis, no broker. +- **Prometheus metrics in-process**. `GET /metrics` text exposition, optional bearer token. No log-shipping sidecars. +- **REST aggregations**. `count`, `sum`, `avg`, `min`, `max` with `groupBy`, filtered and **list-rule enforced**, computed in SQL so you never ship rows to the client just to count them. → `GET /api/v1/records/{c}/aggregate?aggregate=count,sum:amount&groupBy=status` +- **Rule-enforced relation expansion**. Forward (`?expand=author`), back-relations (`?expand=comments_via_post`), and nested paths (`comments_via_post.user`, up to 6 levels). Every level honors the target collection's rules, so a public collection can never leak a locked one through expand. +- **Anonymous sign-in**. `POST /api/v1/collections/{c}/auth/auth-with-anonymous` mints a throwaway user for try-before-signup flows. Off by default per collection; gate rules with `@request.auth.anonymous = false`. +- **Passkeys (WebAuthn)**. Phishing-resistant, passwordless sign-in for auth collections, in-process via go-webauthn. Off by default per collection (`allowPasskeys`); discoverable credentials, no email enumeration, SDK helpers included. → `POST /api/v1/collections/{c}/auth/passkey/login-begin` +- **WAL change capture**. An opt-in logical-replication consumer (`realtime_wal_enabled`) so rows changed by *direct SQL* (psql, the SQL console, the generated RLS role) reach realtime subscribers too, rule-checked like everything else. Captured in-process via `pglogrepl`; no separate change-data-capture service. +- **Resumable uploads (TUS)**. `POST /api/v1/files/tus/` attaches large files to records over flaky connections with full rule + field-constraint enforcement. No upload sidecar to run. +- **Schema migrations as files**. Dev-mode collection changes are snapshotted to `./gb_migrations/*.json` and replayed on boot, so schemas live in git and deploy reproducibly (`gresbase migrations snapshot|list|apply`). +- **Editable email templates**. Every transactional email customizable from the dashboard, validated at save time, with built-in defaults as a can't-break fallback. +- **Realtime broadcast + presence**. Client-to-client channel messages (`POST /api/v1/realtime/broadcast`, auth required) and per-channel presence with join/leave events and member state. Travels across nodes over `LISTEN/NOTIFY` in multi-node mode. +- **On-the-fly image transforms**. `?thumb=400x300f&format=jpeg&quality=80` on file URLs, generated in-process and cached. No image-proxy sidecar to run. +- **File-based JS hooks with hot reload**. Drop `*.js` files in `./gb_hooks` to handle record/auth events; edits reload live. No Deno runtime, no cold starts. Scaffold with `gresbase hooks init` (includes `types.d.ts` for editor autocomplete). +- **Read-replica routing**. Set `DATABASE_REPLICA_URL` and record lists, aggregations, and relation expansion route to a PostgreSQL read replica; writes and rule-feeding reads stay on the primary. The scale lever a single-writer SQLite backend structurally cannot offer. +- **Embed as a Go framework**. Write hooks in real Go and add custom routes; see [`backend/examples/embed`](backend/examples/embed). 📖 **Full features guide**: [docs/FEATURES.md](docs/FEATURES.md) [![Go Version](https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go)](https://go.dev) -[![TypeScript](https://img.shields.io/badge/TypeScript-5.6-3178C6?style=flat&logo=typescript)](https://www.typescriptlang.org) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.6%2B-3178C6?style=flat&logo=typescript)](https://www.typescriptlang.org) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Tests](https://img.shields.io/badge/tests-passing-brightgreen)](https://github.com/gresbase/gresbase) +[![CI](https://github.com/Sulaiman-Dauda/gresbase/actions/workflows/ci.yml/badge.svg)](https://github.com/Sulaiman-Dauda/gresbase/actions/workflows/ci.yml) --- @@ -55,21 +57,21 @@ larger platform does with a sidecar service, Gresbase does with a Postgres featu A **single-binary, self-hosted backend platform** built on PostgreSQL: -- **Dynamic Collections** — Define PostgreSQL-backed schemas from the dashboard or API, with locked-by-default access rules. -- **Authentication** — Email/password, OAuth, magic links, OTP, passkeys, API keys, and end-user record auth. -- **Realtime Engine** — SSE/WebSocket record subscriptions with per-subscriber rule enforcement. -- **File Storage** — Local or S3-compatible storage, with downloads gated by collection view rules. -- **Admin Dashboard** — Next.js + Tailwind CSS + shadcn/ui, embedded in the binary. -- **Extensible** — Event hooks on every request path, Go hooks, and file-based JS hooks (`./gb_hooks`) with hot reload. +- **Dynamic Collections**. Define PostgreSQL-backed schemas from the dashboard or API, with locked-by-default access rules. +- **Authentication**. Email/password, OAuth, magic links, OTP, passkeys, API keys, and end-user record auth. +- **Realtime Engine**. SSE/WebSocket record subscriptions with per-subscriber rule enforcement. +- **File Storage**. Local or S3-compatible storage, with downloads gated by collection view rules. +- **Admin Dashboard**. Next.js + Tailwind CSS + shadcn/ui, embedded in the binary. +- **Extensible**. Event hooks on every request path, Go hooks, and file-based JS hooks (`./gb_hooks`) with hot reload. -Everything runs in one process against one PostgreSQL database — no extra services, brokers, or sidecars. +Everything runs in one process against one PostgreSQL database, with no extra services, brokers, or sidecars. ## Quick Start ### From Source (recommended for development) ```bash -git clone https://github.com/gresbase/gresbase +git clone https://github.com/Sulaiman-Dauda/gresbase cd gresbase # Build everything (frontend + backend → single binary) @@ -106,7 +108,7 @@ DATABASE_URL="postgres://user:pass@host:5432/gresbase?sslmode=disable" \ ``` ┌──────────────────────────────────────────────────────────────┐ -│ Gresbase — Single Static Binary │ +│ Gresbase - Single Static Binary │ ├──────────────────────────────────────────────────────────────┤ │ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌───────────────┐ │ │ │ API │ │ Realtime│ │ Storage │ │ Admin UI │ │ @@ -121,7 +123,7 @@ DATABASE_URL="postgres://user:pass@host:5432/gresbase?sslmode=disable" \ │ ├──────────┬──────────┬──────────┬─────────────────────┤ │ │ │ Auth │Collection│ Storage │ Migrations │ │ │ ├──────────┼──────────┼──────────┼─────────────────────┤ │ -│ │ PostgreSQL (pgx) — embedded or external │ │ +│ │ PostgreSQL (pgx) - embedded or external │ │ │ └──────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ @@ -132,8 +134,8 @@ instance auto-starts for zero-setup development. > **Honest note on embedded PostgreSQL:** the embedded mode downloads > platform-specific PostgreSQL binaries on first run (internet required) and is -> intended for development and small deployments. For production — and for -> air-gapped environments — point `DATABASE_URL` at a managed or self-run +> intended for development and small deployments. For production (and for +> air-gapped environments) point `DATABASE_URL` at a managed or self-run > PostgreSQL. pgvector features also require external PostgreSQL. ## Features @@ -160,7 +162,7 @@ Dynamic collections backed by real PostgreSQL tables: - **Field types**: text, number, bool, email, url, date, select, json, file, relation, password, editor, geo_point, autodate - **Schema migration**: Changing a collection schema automatically alters the underlying PostgreSQL table -- **Access rules — locked by default**: +- **Access rules, locked by default**: - `null` (or omitted) → **locked**: only superusers can perform the operation - `""` → **public**: anyone can perform the operation - `"owner = @request.auth.id"` → filter expression evaluated per request/record @@ -182,8 +184,8 @@ POST /api/v1/admin/users # Manage admin users ``` - JWT access + refresh tokens (HS256 or ES256) -- **Passkeys (WebAuthn)** — phishing-resistant passwordless sign-in, per-collection opt-in -- **30 OAuth2 providers** — Google, GitHub, Microsoft, GitLab, Discord, Apple, Facebook, Spotify, Twitch, Notion, Slack, LinkedIn, Kakao, VK, Yandex, and more; configure any with `_CLIENT_ID` / `_CLIENT_SECRET` +- **Passkeys (WebAuthn)**. Phishing-resistant passwordless sign-in, per-collection opt-in +- **30 OAuth2 providers**. Google, GitHub, Microsoft, GitLab, Discord, Apple, Facebook, Spotify, Twitch, Notion, Slack, LinkedIn, Kakao, VK, Yandex, and more; configure any with `_CLIENT_ID` / `_CLIENT_SECRET` - Session management with revocation - Rate limiting on auth endpoints - API keys with prefix `gb_` @@ -205,7 +207,7 @@ ws.onmessage = (event) => { ``` - Gresbase record topics (`posts/*`, `posts/{id}`) -- **Access rules enforced per subscriber** — events from locked/filtered collections are only delivered to clients the rules allow (fail-closed) +- **Access rules enforced per subscriber**. Events from locked/filtered collections are only delivered to clients the rules allow (fail-closed) - Per-subscription filter expressions and field picking - Cryptographically random client IDs, per-client subscription caps, reserved server event names - SSE primary, WebSocket fallback; automatic reconnection; 10k+ concurrent connections @@ -214,20 +216,20 @@ ws.onmessage = (event) => { - Local filesystem or S3-compatible backends - Signed URLs for secure access -- **Auto-thumbnails** — `?thumb=100x100` (center crop), `100x100t` (top), `100x100f` (fit); generated on demand, cached, concurrency-capped -- **File tokens** — `POST /api/v1/files/token` mints a 3-minute token so protected files work in ``/`