# Signature example

```sh
bunx @revnu/fuast new nda --template signature
```

Share an agreement or PDF with your team and collect signatures. Each signature is tied to the signer's verified fuast account.

## What it shows

- `auth: { requireSignIn: true }`: no guests, because a signature needs a real identity.
- `documents` and `signatures` tables linked by `docId: id("documents")`, with a `by_doc_signer` index that enforces one signature per person.
- Attachments and signature images uploaded with `client.storage.upload`. Rows store only the storage key, and the `document` query turns keys into URLs with `ctx.storage.url()`.
- Client-sent keys checked with `ctx.storage.stat()` before a row points at them: the object must exist and the caller must have uploaded it, so nobody can attach (and later delete) someone else's file.
- A `signatureCount` field updated in the same transaction as each signature, so the list never counts rows.
- An email to the author for every new signature, sent as the last step of the mutation.
- `deleteDocument` removes rows first and storage objects last, because storage is outside the transaction.
- Client routes `/` and `/docs/:id`, and a dependency-free canvas signature pad.

## Upload, then write

```tsx
const uploaded = await client.storage.upload(png);   // private by default
await sign(docId, uploaded.key);
```

```ts
async function uploadedBy(ctx: { storage: StorageContext }, key: string, userId: string) {
  if (!isStorageKey(key)) return false;
  const info = await ctx.storage.stat(key);           // null when the object does not exist
  return info !== null && info.uploadedBy === userId;
}

sign: mutation(async (ctx, docId: string, imageKey: string) => {
  const me = ctx.auth.requireSignedIn();
  if (!(await uploadedBy(ctx, imageKey, me.userId))) throw new Error("Draw your signature again.");
  const doc = await ctx.db.documents.get(docId);
  if (!doc) throw new Error("This document no longer exists.");
  const existing = await ctx.db.signatures
    .withIndex("by_doc_signer", (q) => q.eq("docId", docId).eq("signerId", me.userId))
    .first();
  if (existing) throw new Error("You already signed this document.");
  await ctx.db.signatures.insert({ docId, signerId: me.userId, name: me.displayName, imageKey });
  await ctx.db.documents.update(docId, { signatureCount: doc.signatureCount + 1 });
  // email the author last
})
```

The signer's name and email come from `ctx.auth`, so nobody can sign as someone else. `createDocument` checks the attachment key the same way.

## Try it

```sh
bunx @revnu/fuast dev
```

Create a document as `dev`, then open it as `?fuast_user=alice` and sign. The author's email appears in `bunx @revnu/fuast logs --port 3000`. Deploy and share with your workspace:

```sh
bunx @revnu/fuast deploy
bunx @revnu/fuast share --visibility workspace
```

Add an outside signer with `--emails them@partner.com`.
