invoice-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 333971bf96d2
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone b3b80918bc912dda8c53a48d
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
policy.json
{
  "constants": {
    "AGENT_ADDRESS": "5NoJ1zZdh5bYyiyjnN9mC8RxN3kU2XdkjUJui3u9qrS3",
    "AGENT_ADDRESS_BOUND": true
  },
  "auth": {
    "anonymous": true
  },
  "invoices/$invoiceId": {
    "description": "A shared invoice. data is a JSON string with every invoice field (the logo is a small data URL); total is in the currency's minor unit. Readable by anyone who opened its link (a join below); only owners can change it.",
    "fields": {
      "data": "String",
      "total": "UInt",
      "currency": "String",
      "status": "String",
      "paidAt": "UInt?",
      "createdBy": "String!",
      "createdAt": "UInt!"
    },
    "rules": {
      "read": "@user.id != null && get(/invoices/$invoiceId/joins/@user.id) != null",
      "create": "@user.id != null && @newData.createdBy == @user.id && @newData.createdAt <= @time.now && @StringUtils.length(@newData.data) <= 200000 && @StringUtils.length(@newData.currency) == 3 && @newData.status == 'unpaid' && getAfter(/invoices/$invoiceId/joins/@user.id) != null && getAfter(/invoices/$invoiceId/owners/@user.id) != null && getAfter(/invoicekeys/$invoiceId) != null",
      "update": "@user.id != null && get(/invoices/$invoiceId/owners/@user.id) != null && @StringUtils.length(@newData.data) <= 200000 && @StringUtils.length(@newData.currency) == 3 && (@newData.status == 'unpaid' || @newData.status == 'paid')",
      "delete": "false"
    }
  },
  "invoices/$invoiceId/joins/$userId": {
    "description": "Someone who opened the invoice's link on a device. Knowing the invoice id is what lets you join and read it.",
    "fields": {
      "at": "UInt!"
    },
    "rules": {
      "read": "@user.id != null && @user.id == $userId",
      "create": "@user.id != null && @user.id == $userId && @newData.at <= @time.now && getAfter(/invoices/$invoiceId) != null",
      "update": "false",
      "delete": "false"
    }
  },
  "invoices/$invoiceId/owners/$userId": {
    "description": "Someone who can edit the invoice and mark it paid: its creator, or anyone who presented its admin key.",
    "fields": {
      "at": "UInt!"
    },
    "rules": {
      "read": "@user.id != null && get(/invoices/$invoiceId/joins/@user.id) != null",
      "create": "@user.id != null && @user.id == $userId && @newData.at <= @time.now && ((get(/invoices/$invoiceId) == null && getAfter(/invoices/$invoiceId).createdBy == @user.id) || (get(/invoicekeys/$invoiceId) != null && getAfter(/invoiceclaims/$invoiceId/c/@user.id).key == get(/invoicekeys/$invoiceId).key))",
      "update": "false",
      "delete": "false"
    }
  },
  "invoicekeys/$invoiceId": {
    "description": "The invoice's admin key, set once by its creator. Only the public API can read it, to check a key someone presents.",
    "fields": {
      "key": "String!"
    },
    "rules": {
      "read": "@user.id == @const.BOUNDED_PUBLIC_PRINCIPAL_API",
      "create": "@user.id != null && get(/invoices/$invoiceId) == null && getAfter(/invoices/$invoiceId).createdBy == @user.id && @StringUtils.length(@newData.key) >= 20",
      "update": "false",
      "delete": "false"
    }
  },
  "invoiceclaims/$invoiceId/c/$userId": {
    "description": "An admin key someone presented to become an owner. Nobody can read it back.",
    "fields": {
      "key": "String"
    },
    "rules": {
      "read": "false",
      "create": "@user.id != null && @user.id == $userId",
      "update": "@user.id != null && @user.id == $userId",
      "delete": "false"
    }
  },
  "functions": {
    "api": {
      "auth": "true",
      "entry": "functions/api.ts",
      "public": true,
      "methods": [
        "GET",
        "POST",
        "PATCH"
      ],
      "cors": "passthrough"
    }
  }
}

Change history

Quality pass: phone-readable invoice, estimates, email client, saved clients, CSV export, openapi.json
333971bf96 · 4 hours ago
Bounded source sync
088b53bddb · 1 day ago
Bounded source sync
3f3e9ff8f9 · 1 day ago
Bounded source sync
209bec4267 · 1 day ago
Bounded source sync
11f3d98cda · 1 day ago
Bounded source sync
722948ebc3 · 1 day ago
Bounded source sync
3e9d04b2c0 · 1 day ago