Skip to content

Repository files navigation

docstore

Digital Document Wallet with Integrity Verification. A personal/institutional repository for digital documents with cryptographic integrity verification via SHA-512. Users prove that a file has not been modified since its registration point by comparing a stored hash against a recomputed one.

Features

  • Document registration with automatic SHA-512 hash computation (streaming, no full-file buffering)
  • Integrity verification: Intact / Divergent / Unverifiable / AlgorithmUnsupported / RecordNotFound
  • Document versioning — new version linked to predecessor, prior content immutable
  • Controlled sharing via expiring, revocable tokens scoped to a specific document version
  • Revocation with justification and full audit trail
  • Append-only audit log (INSERT-only at the PostgreSQL permission level)
  • RBAC — Document Owner, Institutional Issuer, Third Party (Validator), System Administrator

Stack

Layer Technology
Language Go 1.26
HTML templating templ
Frontend reactivity AlpineJS
Styling TailwindCSS 4
Partial updates HTMX
JS/CSS bundler Bun
Database PostgreSQL
DB access sqlc + pgx/v5
Migrations goose
Router chi
Hot reload air
Task runner just

Prerequisites

  • mise (manages Go, Bun, just)
  • Docker (for local PostgreSQL via docker compose)

Install tool versions:

mise install

Setup

# Install JS dependencies and Go tool dependencies
just deps

# Start local PostgreSQL
just db-up

# Run migrations
just migrate

Copy .env.example to .env (once it exists) and set:

DSN=postgres://user:password@localhost:5432/docstore?sslmode=disable

Development

just dev

This starts five parallel watchers:

Watcher What it does
live/templ Regenerates templ components, proxies :8080, hot-reloads browser
live/server air — rebuilds and restarts Go binary on .go changes
live/tailwind Rebuilds out.css on CSS changes
live/esbuild Bundles index.jsweb/static/js/ on JS changes
live/sync-assets Notifies templ proxy on JS/CSS asset changes

The app listens on :8080; the browser dev proxy is on :7331.

Common Commands

just build          # production binary (./cmd/server)
just generate       # templ + sqlc codegen
just test           # full test suite with race detector
just test-one NAME  # single test: just test-one TestFooBar
just migrate        # apply pending migrations (goose up)
just migrate-status # show migration status
just migrate-down   # roll back one migration
just css            # one-shot Tailwind build
just deps           # install all dependencies

Architecture

Code is organized by feature (vertical slice), not by technical layer. Each slice owns its handler, service logic, and types end-to-end.

cmd/server/              # main entrypoint — registers routes, wires slices

internal/
  upload/                # document registration + hash (RF01–RF03)
  verify/                # integrity check (RF05)
  versioning/            # new version linked to predecessor (RF07)
  revocation/            # revoke with justification + audit (RF08)
  sharing/               # token generation, expiry, public verify (RF06)
  collection/            # type/tag/collection organization (RF04)
  receipt/               # integrity receipt issuance (RF09)
  export/                # export document + integrity records (RF12)
  importdoc/             # import with external hash (RF13)
  expiration/            # background worker — no handler (RF11)

  platform/
    auth/                # User, Role, password hashing
    session/             # HMAC-signed PostgreSQL session store
    audit/               # append-only audit writer

  store/
    db/                  # sqlc-generated code (do not edit by hand)
    migrations/          # goose files (sequential: 00001_*.sql)
    queries/             # sqlc SQL source (one file per slice)

  testhelper/            # shared testcontainers-go + goose runner

web/
  templates/             # .templ files mirroring slice names
  static/                # Bun build output (CSS, JS)

Slices do not import each other. Shared types come from store/db models or internal/types.go.

Slice anatomy

upload/
  handler.go    # chi handler — reads request, calls service, renders templ
  service.go    # business logic — depends on store/db.Querier + platform/audit
  types.go      # request/response types, view models, domain errors

Go Tooling

Dev tools live in tools/ with their own go.mod to keep the main module clean:

# Run a tool directly
go tool -modfile=tools/go.mod templ generate
go tool -modfile=tools/go.mod sqlc generate

# Add a new tool
cd tools && go get -tool github.com/some/tool@latest

go generate ./... uses //go:generate directives that invoke tools via -modfile=tools/go.mod.

Database

Local PostgreSQL is managed via docker compose:

just db-up      # start
just db-down    # stop

Migrations use goose with sequential numeric naming (00001_create_users.sql). Never edit an applied migration — always add a new file.

All DB access goes through sqlc-generated code in store/db/. No raw query strings in .go files.

Testing

Integration tests via testcontainers-go are the primary testing strategy. Each test spins up a real PostgreSQL container, runs all migrations, and runs inside a pgx.Tx that is rolled back in t.Cleanup.

Unit tests are reserved for pure logic with zero I/O: hash computation, HMAC verification, TTL math.

just test                    # all tests, race detector, no cache
just test-one TestVerifyHash # single test

Session Security

Sessions use opaque random tokens — a DB breach yields hashes that cannot be reversed to valid cookie values:

  • Cookie value: hex(crypto/rand 32 bytes) — 256 bits of entropy
  • DB stores sha256(token) — the raw token never persists server-side
  • Invalid or tampered tokens simply miss in the DB

Integrity Pipeline

Upload → io.TeeReader(file, sha512.New()) → write bytes to storage
       → single pgx.Tx:
           INSERT documents
           INSERT integrity_records (version_id, algorithm, digest, computed_at)
           INSERT audit_log (actor_id, action, ref_id, ts)

Verify → fetch stored (digest, algorithm) for version_id
       → recompute digest (streaming)
       → subtle.ConstantTimeCompare
       → Result: Intact | Divergent | Unverifiable | AlgorithmUnsupported | RecordNotFound

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages