{"id":49484771,"url":"https://github.com/rendis/doc-assembly","last_synced_at":"2026-05-01T00:30:36.855Z","repository":{"id":338917141,"uuid":"1159545842","full_name":"rendis/doc-assembly","owner":"rendis","description":"Multi-tenant document template builder with digital signatures — Go library with embedded React SPA, TipTap editor, and Typst PDF rendering.","archived":false,"fork":false,"pushed_at":"2026-04-22T13:44:10.000Z","size":19367,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-01T00:30:34.862Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rendis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-16T21:24:04.000Z","updated_at":"2026-04-22T13:44:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rendis/doc-assembly","commit_stats":null,"previous_names":["rendis/doc-assembly"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rendis/doc-assembly","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rendis%2Fdoc-assembly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rendis%2Fdoc-assembly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rendis%2Fdoc-assembly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rendis%2Fdoc-assembly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rendis","download_url":"https://codeload.github.com/rendis/doc-assembly/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rendis%2Fdoc-assembly/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32481553,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"ssl_error","status_checked_at":"2026-04-30T13:12:06.837Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-01T00:30:36.231Z","updated_at":"2026-05-01T00:30:36.832Z","avatar_url":"https://github.com/rendis.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# doc-assembly\n\n**Multi-tenant document template builder with digital signature delegation.**\n\nGo 1.25 \u0026middot; React 19 \u0026middot; PostgreSQL 16 \u0026middot; Typst\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Monorepo Structure](#monorepo-structure)\n- [Architecture](#architecture)\n- [Quick Start](#quick-start)\n- [Available Commands](#available-commands)\n- [Configuration](#configuration)\n- [Digital Signatures](#digital-signatures)\n- [Background Workers (River)](#background-workers-river)\n- [Database](#database)\n- [Deployment](#deployment)\n- [Documentation](#documentation)\n- [License](#license)\n\n## Overview\n\ndoc-assembly lets organizations build reusable document templates with a rich text editor, inject dynamic data through a variable system, render production-quality PDFs via Typst, and delegate digital signatures to external providers.\n\nThe platform is multi-tenant by design: a three-level RBAC model (System, Tenant, Workspace) controls access, while the backend enforces tenant isolation across every query.\n\n### Key Features\n\n- Rich text template editor (TipTap) with injectable variables and signature blocks\n- PDF rendering powered by Typst (no browser/Chromium dependency)\n- Digital signature delegation to Documenso (PandaDoc and DocuSign interfaces planned)\n- Three-level RBAC: System, Tenant, and Workspace roles\n- Template versioning with publish/archive lifecycle\n- Extensibility system via code-generated injectors and mappers\n- Internationalization (English \u0026 Spanish)\n- Folder and tag organization for templates\n\n## Monorepo Structure\n\n```plaintext\ndoc-assembly/\n  core/       Go backend (Hexagonal Architecture, Gin, Wire DI)\n  app/        React SPA (TanStack Router, Zustand, TipTap)\n  db/         Liquibase migrations (PostgreSQL 16)\n  docs/       All project documentation\n  scripts/    Tooling (docml2json, etc.)\n```\n\n| Component      | Stack                                                    | Docs                              |\n| -------------- | -------------------------------------------------------- | --------------------------------- |\n| **doc-engine** | Go 1.25, Gin, pgx/v5, Wire                               | [Backend docs](docs/backend/)     |\n| **web-client** | React 19, TypeScript, TanStack Router, Zustand, TipTap 3 | [Frontend docs](docs/frontend/)   |\n| **db**         | Liquibase, PostgreSQL 16, pgcrypto                       | [DATABASE.md](db/DATABASE.md)     |\n| **scripts**    | Python 3 tooling                                         | [docml2json](scripts/docml2json/) |\n\n## Architecture\n\n### Request Flow (Hexagonal)\n\n```plaintext\nHTTP Request\n -\u003e Middleware (JWT auth, tenant context, operation ID)\n   -\u003e Controller (parse DTO, validate)\n     -\u003e UseCase interface\n       -\u003e Service (business logic)\n         -\u003e Port interface\n           -\u003e Repository (SQL via pgx)\n             -\u003e PostgreSQL\n```\n\n### Multi-Tenant Hierarchy\n\n```plaintext\nSystem\n +-- Tenant A\n |    +-- Workspace 1\n |    +-- Workspace 2\n +-- Tenant B\n      +-- Workspace 3\n```\n\n### RBAC\n\nThree authorization levels with seven roles:\n\n| Level         | Roles                                            |\n| ------------- | ------------------------------------------------ |\n| **System**    | `SUPERADMIN`, `PLATFORM_ADMIN`                   |\n| **Tenant**    | `OWNER`, `ADMIN`                                 |\n| **Workspace** | `OWNER`, `ADMIN`, `EDITOR`, `OPERATOR`, `VIEWER` |\n\n`SUPERADMIN` auto-elevates to `OWNER` in any tenant or workspace.\n\n## Quick Start\n\n### Prerequisites\n\n| Tool          | Version | Install                                                         |\n| ------------- | ------- | --------------------------------------------------------------- |\n| Go            | 1.25+   | [go.dev/dl](https://go.dev/dl/)                                 |\n| Node.js       | 20+     | [nodejs.org](https://nodejs.org/)                               |\n| pnpm          | 9+      | `npm i -g pnpm`                                                 |\n| Docker        | latest  | [docker.com](https://www.docker.com/)                           |\n| Typst         | 0.13+   | [typst.app](https://github.com/typst/typst/releases)            |\n| golangci-lint | latest  | [golangci-lint.run](https://golangci-lint.run/welcome/install/) |\n| Wire          | latest  | `go install github.com/google/wire/cmd/wire@latest`             |\n\nRun `make doctor` to verify all dependencies are installed.\n\n### 1. Clone and install dependencies\n\n```bash\ngit clone https://github.com/your-org/doc-assembly.git\ncd doc-assembly\npnpm install --dir apps/web-client\ngo -C apps/doc-engine mod download\n```\n\n### 2. Start PostgreSQL\n\n```bash\ndocker compose -f docker-compose.dev.yml up -d\n```\n\nThis starts PostgreSQL 16 on port 5432.\n\n### 3. Run database migrations\n\n```bash\ncd db \u0026\u0026 ./run-migrations.sh \u0026\u0026 cd ..\n```\n\n### 4. Start dev servers\n\n```bash\nmake dev-dummy\n```\n\n\u003e [!TIP]\n\u003e `dev-dummy` bypasses JWT authentication so you can develop without setting up Keycloak. The backend runs on `:8080` and the frontend on `:3001`.\n\nThe app is now available at **\u003chttp://localhost:3001\u003e**. The first user to sign up is automatically promoted to `SUPERADMIN`.\n\n## Available Commands\n\nRun `make help` for the full list.\n\n| Command                 | Description                              |\n| ----------------------- | ---------------------------------------- |\n| `make dev`              | Hot reload backend + frontend            |\n| `make dev-dummy`        | Dev with dummy auth (no Keycloak needed) |\n| `make build`            | Build backend + frontend                 |\n| `make test`             | Run unit tests                           |\n| `make test-integration` | Run integration tests (Docker required)  |\n| `make lint`             | Lint backend + frontend                  |\n| `make gen`              | Codegen: Wire DI + Swagger + Extensions  |\n| `make doctor`           | Check system dependencies                |\n| `make clean`            | Remove all build artifacts               |\n\nPass `DUMMY=1` to any target to enable dummy auth: `make run DUMMY=1`.\n\n## Configuration\n\n### Backend\n\nConfiguration is loaded from `apps/doc-engine/settings/app.yaml` and can be overridden with environment variables following the pattern `DOC_ENGINE_\u003cSECTION\u003e_\u003cKEY\u003e`.\n\nCopy the example env file and fill in values:\n\n```bash\ncp apps/doc-engine/.env.example apps/doc-engine/.env\n```\n\nKey variables:\n\n| Variable                          | Required | Description                           |\n| --------------------------------- | -------- | ------------------------------------- |\n| `DOC_ENGINE_DATABASE_PASSWORD`    | Yes      | PostgreSQL password                   |\n| `DOC_ENGINE_AUTH_JWKS_URL`        | Yes\\*    | Keycloak JWKS endpoint                |\n| `DOC_ENGINE_AUTH_ISSUER`          | Yes\\*    | Keycloak issuer URL                   |\n| `DOC_ENGINE_AUTH_AUDIENCE`        | Yes\\*    | JWT audience claim                    |\n| `DOC_ENGINE_AUTH_DUMMY`           | No       | Set `true` to bypass JWT              |\n| `DOC_ENGINE_DOCUMENSO_API_KEY`    | No       | Documenso API key (for signing)       |\n\n\\* Not required when `DOC_ENGINE_AUTH_DUMMY=true`.\n\n\u003e [!NOTE]\n\u003e See [`apps/doc-engine/settings/app.yaml`](apps/doc-engine/settings/app.yaml) for all available options including storage, logging, scheduler, Typst renderer, and notification settings.\n\n### Frontend\n\n```bash\ncp apps/web-client/.env.example apps/web-client/.env\n```\n\n| Variable                  | Default                 | Description                    |\n| ------------------------- | ----------------------- | ------------------------------ |\n| `VITE_API_URL`            | `/api/v1`               | Backend API base URL           |\n| `VITE_KEYCLOAK_URL`       | `http://localhost:8180` | Keycloak server URL            |\n| `VITE_KEYCLOAK_REALM`     | `doc-assembly`          | Keycloak realm                 |\n| `VITE_KEYCLOAK_CLIENT_ID` | `web-client`            | Keycloak client ID             |\n| `VITE_USE_MOCK_AUTH`      | `true`                  | Bypass Keycloak in development |\n\n## Digital Signatures\n\ndoc-assembly delegates digital signatures to external providers. Each document has a **shared public URL** (`/public/doc/{id}`) that recipients use to verify their email and receive a signing link.\n\n```plaintext\nTemplate (published)\n  -\u003e Admin creates document\n    -\u003e Recipients notified with public URL (/public/doc/{id})\n      -\u003e Recipient visits URL, enters email\n        -\u003e System verifies email, sends token link (/public/sign/{token})\n          -\u003e Path A (no interactive fields): PDF preview -\u003e Sign\n          -\u003e Path B (interactive fields): Fill form -\u003e PDF preview -\u003e Sign\n            -\u003e Signing provider handles signature -\u003e Webhooks update status -\u003e Sealed PDF stored\n```\n\nFor detailed flow documentation with sequence diagrams, see **[Public Signing Flow](docs/backend/public-signing-flow.md)**.\n\n### Supported Providers\n\n| Provider                            | Status            |\n| ----------------------------------- | ----------------- |\n| [Documenso](https://documenso.com/) | Implemented       |\n| PandaDoc                            | Interface defined |\n| DocuSign                            | Interface defined |\n\n### Local Documenso Setup\n\n```bash\ndocker compose -f docker-compose.documenso.yml up -d\n```\n\n\u003e [!IMPORTANT]\n\u003e The compose file includes a `documenso-cert-init` service that auto-generates a self-signed P12 certificate for document sealing. No manual certificate setup is needed.\n\nThis starts:\n\n- **Documenso** on `http://localhost:3000`\n- **MailPit** (SMTP) on `http://localhost:8025` (web UI) and `:1025` (SMTP)\n- **PostgreSQL** for Documenso on port `5433`\n\nConfigure the webhook in Documenso to point to `http://host.docker.internal:8080/webhooks/signing/documenso`.\n\n## Background Workers (River)\n\nSigning execution is attempt-scoped and durable. `execution.documents` is the business projection, while `execution.signing_attempts` is the technical source of truth for render, provider submission, retry/reconciliation, cleanup, refresh, and completion dispatch. River runs inside the API process and stores jobs in PostgreSQL; no external broker is required.\n\n### Attempt-scoped jobs\n\n`ProceedToSigning` creates or reuses the active signing attempt and enqueues River work transactionally. Provider upload does **not** happen inline in the public/authenticated request path.\n\n```plaintext\n/public/sign/{token}/proceed\n  -\u003e create/reuse active SigningAttempt\n  -\u003e INSERT river_job(render_attempt_pdf) in same PostgreSQL transaction\n  -\u003e frontend receives step=processing and polls\n\nRiver render_attempt_pdf\n  -\u003e render immutable pre-signed PDF\n  -\u003e persist PDF path/checksum + enqueue submit_attempt_to_provider in one transaction\n\nRiver submit_attempt_to_provider\n  -\u003e submit PDF snapshot to provider using correlation key {document_id}:{attempt_id}\n  -\u003e persist provider IDs + READY_TO_SIGN projection\n\nProvider webhook\n  -\u003e resolve active attempt\n  -\u003e update attempt + document projection\n  -\u003e enqueue dispatch_attempt_completion in the same transaction\n```\n\n### Key properties\n\n| Property | Behavior |\n|----------|----------|\n| **Source of truth** | `SigningAttempt` owns technical signing state; `Document` exposes only the current business projection via `active_attempt_id`. |\n| **Atomic enqueue** | Attempt state transitions and next River job enqueue happen in one PostgreSQL transaction. |\n| **Idempotency** | Jobs are unique by `attempt_id + phase`; stale jobs no-op when they no longer match the document active attempt. |\n| **Recovery** | Transient provider failures retry the same attempt; ambiguous provider results reconcile by correlation key or move to review. |\n| **Regeneration** | A new attempt supersedes the old one; historical attempts and late webhooks cannot mutate the active document projection. |\n| **Completion dispatch** | Completion events are still delivered through River, but the dispatch job is attempt-aware and checks `document.active_attempt_id`. |\n\n### SDK Usage\n\n```go\nimport \"github.com/rendis/doc-assembly/core/sdk\"\n\nhandler := func(ctx context.Context, ev sdk.DocumentCompletedEvent) error {\n    log.Printf(\"Document %s completed in tenant %s\", ev.DocumentID, ev.TenantCode)\n    for _, r := range ev.Recipients {\n        log.Printf(\"  %s (%s) signed at %v\", r.Name, r.RoleName, r.SignedAt)\n    }\n    return nil // return error to retry completion dispatch\n}\n```\n\n### Configuration\n\n```yaml\nworker:\n  enabled: false              # DOC_ENGINE_WORKER_ENABLED\n  max_workers: 10             # DOC_ENGINE_WORKER_MAX_WORKERS\n  runtime_environment: local  # DOC_ENGINE_WORKER_RUNTIME_ENVIRONMENT\n  failpoints_enabled: false   # DOC_ENGINE_WORKER_FAILPOINTS_ENABLED (dev/test only)\n  failpoints: []              # DOC_ENGINE_WORKER_FAILPOINTS\n```\n\nFor architecture diagrams, attempt job details, failpoints, and integration test coverage, see **[Worker Queue Guide](docs/backend/worker-queue-guide.md)**.\n\n## Database\n\nPostgreSQL 16 with five schemas:\n\n| Schema      | Purpose                                        |\n| ----------- | ---------------------------------------------- |\n| `tenancy`   | Tenants, workspaces, memberships               |\n| `identity`  | Users, access history                          |\n| `organizer` | Folders, tags                                  |\n| `content`   | Templates, versions, injectables, signer roles |\n| `execution` | Documents, recipients, signing attempts, events |\n\nMigrations are managed with Liquibase:\n\n```bash\ncd db \u0026\u0026 ./run-migrations.sh\n```\n\n\u003e [!WARNING]\n\u003e Do not modify migration files in `db/src/` directly. Suggest changes and create new changesets instead.\n\nSee [`db/DATABASE.md`](db/DATABASE.md) for the complete schema documentation.\n\n## Deployment\n\n### Docker Build\n\n```bash\ndocker build -f apps/doc-engine/Dockerfile -t doc-engine .\n```\n\nThe Dockerfile uses a multi-stage build:\n\n1. **Builder**: `golang:1.25-alpine` compiles the binary\n2. **Runtime**: `alpine:3.21` with Typst v0.13.1 and ca-certificates\n\nThe container exposes port `8080`.\n\n### Required Environment Variables (Production)\n\n| Category     | Variables                                                          |\n| ------------ | ------------------------------------------------------------------ |\n| **Database** | `DOC_ENGINE_DATABASE_HOST`, `_PORT`, `_USER`, `_PASSWORD`, `_NAME` |\n| **Auth**     | `DOC_ENGINE_AUTH_JWKS_URL`, `_ISSUER`, `_AUDIENCE`                 |\n| **Signing**  | `DOC_ENGINE_DOCUMENSO_API_URL`, `_API_KEY`, `_WEBHOOK_SECRET`      |\n| **Storage**  | `DOC_ENGINE_STORAGE_BUCKET`, `_REGION` (for S3)                    |\n\n## Documentation\n\n| Document                            | Path                                                                                         |\n| ----------------------------------- | -------------------------------------------------------------------------------------------- |\n| Backend Architecture                | [`docs/backend/architecture.md`](docs/backend/architecture.md)                               |\n| Authentication Guide                | [`docs/backend/authentication-guide.md`](docs/backend/authentication-guide.md)               |\n| Authorization Matrix                | [`docs/backend/authorization-matrix.md`](docs/backend/authorization-matrix.md)               |\n| Public Signing Flow                 | [`docs/backend/public-signing-flow.md`](docs/backend/public-signing-flow.md)                 |\n| Template Preview Flow               | [`docs/template-preview-flow.md`](docs/template-preview-flow.md)                             |\n| Internal API Document Creation Flow | [`docs/internal-api-document-creation-flow.md`](docs/internal-api-document-creation-flow.md) |\n| Public Signing Flow (Flow Detail)   | [`docs/public-signing-flow-detail.md`](docs/public-signing-flow-detail.md)                   |\n| Extensibility Guide                 | [`docs/backend/extensibility-guide.md`](docs/backend/extensibility-guide.md)                 |\n| Signing Attempts + River Guide      | [`docs/backend/worker-queue-guide.md`](docs/backend/worker-queue-guide.md)                   |\n| Frontend Architecture               | [`docs/frontend/architecture.md`](docs/frontend/architecture.md)                             |\n| Design System                       | [`docs/frontend/design-system.md`](docs/frontend/design-system.md)                           |\n| Database Schema                     | [`db/DATABASE.md`](db/DATABASE.md)                                                           |\n| OpenAPI Spec                        | [`core/docs/swagger.yaml`](core/docs/swagger.yaml)                                           |\n| docml2json Reference                | [`scripts/docml2json/DOCML-REFERENCIA.md`](scripts/docml2json/DOCML-REFERENCIA.md)           |\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frendis%2Fdoc-assembly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frendis%2Fdoc-assembly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frendis%2Fdoc-assembly/lists"}