baby-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 c84ed8821371
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 381bd6356d4b794ac2a7c1b8
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": "CQ8Gbrt8vgyVt57sJnckuvnd764r83QiowmKu91SACyx",
    "AGENT_ADDRESS_BOUND": true
  },
  "auth": {
    "anonymous": true
  },
  "households/$h": {
    "description": "A family's shared baby log. Only members can read it. The random id plus a secret form the family key, which is what the family link and the assistant key carry. tz is the family's time zone, used for days in the API's summaries.",
    "fields": {
      "name": "String",
      "tz": "String?",
      "createdBy": "String!",
      "createdAt": "UInt!"
    },
    "rules": {
      "read": "@user.id != null && get(/households/$h/members/@user.id) != null",
      "create": "@user.id != null && @newData.createdBy == @user.id && @newData.createdAt <= @time.now && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 60 && getAfter(/households/$h/members/@user.id) != null && getAfter(/households/$h/owners/@user.id) != null && getAfter(/householdkeys/$h) != null && getAfter(/ownerkeys/$h) != null",
      "update": "@user.id != null && get(/households/$h/members/@user.id) != null && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 60",
      "delete": "false"
    }
  },
  "households/$h/members/$uid": {
    "description": "Someone who logs for this family: a parent, a grandparent, a nanny, or the assistant API. Joining needs the current family key (a claim in the same write), except for the person creating the family. A member can rename or remove themselves; an owner who is still a member can remove anyone.",
    "fields": {
      "name": "String",
      "at": "UInt!",
      "account": "Bool?"
    },
    "rules": {
      "read": "@user.id != null && get(/households/$h/members/@user.id) != null",
      "create": "@user.id != null && @user.id == $uid && @newData.at <= @time.now && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 40 && ((get(/households/$h) == null && getAfter(/households/$h).createdBy == @user.id) || (get(/households/$h) != null && get(/householdkeys/$h) != null && getAfter(/households/$h/claims/@user.id).key == get(/householdkeys/$h).key))",
      "update": "@user.id != null && @user.id == $uid && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 40",
      "delete": "@user.id != null && (@user.id == $uid || (get(/households/$h/owners/@user.id) != null && get(/households/$h/members/@user.id) != null))"
    }
  },
  "households/$h/claims/$uid": {
    "description": "The family key a person last presented. The write only succeeds with the current key, so a successful write proves the caller holds it (the API checks keys this way); joining checks it again. n is a random value so every claim is a real change.",
    "fields": {
      "key": "String",
      "n": "String"
    },
    "rules": {
      "read": "false",
      "create": "@user.id != null && @user.id == $uid && get(/householdkeys/$h) != null && @newData.key == get(/householdkeys/$h).key",
      "update": "@user.id != null && @user.id == $uid && get(/householdkeys/$h) != null && @newData.key == get(/householdkeys/$h).key",
      "delete": "false"
    }
  },
  "households/$h/owners/$uid": {
    "description": "A member who can remove people and replace the family key. The creator is the first owner. The owner key, kept only on the creator's device, lets the same person become an owner again as their signed-in account. Owner powers also need membership, so an owner who is removed keeps no powers.",
    "fields": {
      "at": "UInt!"
    },
    "rules": {
      "read": "@user.id != null && get(/households/$h/members/@user.id) != null",
      "create": "@user.id != null && @user.id == $uid && @newData.at <= @time.now && getAfter(/households/$h/members/$uid) != null && ((get(/households/$h) == null && getAfter(/households/$h).createdBy == @user.id) || (get(/ownerkeys/$h) != null && getAfter(/households/$h/ownerclaims/@user.id).key == get(/ownerkeys/$h).key))",
      "update": "false",
      "delete": "false"
    }
  },
  "households/$h/ownerclaims/$uid": {
    "description": "An owner key a person presented, checked by the owners rule in the same write.",
    "fields": {
      "key": "String",
      "n": "String"
    },
    "rules": {
      "read": "false",
      "create": "@user.id != null && @user.id == $uid",
      "update": "@user.id != null && @user.id == $uid",
      "delete": "false"
    }
  },
  "householdkeys/$h": {
    "description": "The family key's secret part. Members read it to share the family link and the assistant key. An owner can replace it, which turns off the old link and assistant key without affecting people who already joined.",
    "fields": {
      "key": "String"
    },
    "rules": {
      "read": "@user.id != null && get(/households/$h/members/@user.id) != null",
      "create": "@user.id != null && get(/households/$h) == null && getAfter(/households/$h).createdBy == @user.id && @StringUtils.length(@newData.key) >= 24 && @StringUtils.length(@newData.key) <= 64",
      "update": "@user.id != null && get(/households/$h/owners/@user.id) != null && get(/households/$h/members/@user.id) != null && @StringUtils.length(@newData.key) >= 24 && @StringUtils.length(@newData.key) <= 64",
      "delete": "false"
    }
  },
  "ownerkeys/$h": {
    "description": "The owner key, set once when the family is created. Nobody can read it; the owners rule compares against it.",
    "fields": {
      "key": "String!"
    },
    "rules": {
      "read": "false",
      "create": "@user.id != null && get(/households/$h) == null && getAfter(/households/$h).createdBy == @user.id && @StringUtils.length(@newData.key) >= 24 && @StringUtils.length(@newData.key) <= 64",
      "update": "false",
      "delete": "false"
    }
  },
  "households/$h/babies/$b": {
    "description": "A baby in the family. birth is YYYY-MM-DD or empty. Archived babies are hidden but keep their log.",
    "fields": {
      "name": "String",
      "birth": "String",
      "createdAt": "UInt!",
      "archived": "Bool?"
    },
    "rules": {
      "read": "@user.id != null && get(/households/$h/members/@user.id) != null",
      "create": "@user.id != null && getAfter(/households/$h/members/@user.id) != null && @newData.createdAt <= @time.now && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 40 && @StringUtils.length(@newData.birth) <= 10",
      "update": "@user.id != null && get(/households/$h/members/@user.id) != null && @StringUtils.length(@newData.name) >= 1 && @StringUtils.length(@newData.name) <= 40 && @StringUtils.length(@newData.birth) <= 10",
      "delete": "false"
    }
  },
  "households/$h/events/$e": {
    "description": "One log entry for one baby: a breastfeed, bottle, pump, solids, sleep, diaper, growth measurement, medicine, temperature or note. start and end are unix seconds; a breastfeed or sleep with no end is a running timer. data is JSON with the details, in metric units (ml, kg, cm, degrees C). Any member can add or edit; deleting marks deleted.",
    "fields": {
      "baby": "String",
      "type": "String",
      "start": "UInt",
      "end": "UInt?",
      "data": "String",
      "by": "String!",
      "createdAt": "UInt!",
      "deleted": "Bool?"
    },
    "rules": {
      "read": "@user.id != null && get(/households/$h/members/@user.id) != null",
      "create": "@user.id != null && getAfter(/households/$h/members/@user.id) != null && @newData.by == @user.id && @newData.createdAt <= @time.now && @newData.start <= @time.now + 3600 && (@newData.type == 'breast' || @newData.type == 'bottle' || @newData.type == 'pump' || @newData.type == 'solids' || @newData.type == 'sleep' || @newData.type == 'diaper' || @newData.type == 'growth' || @newData.type == 'medicine' || @newData.type == 'temp' || @newData.type == 'note') && @StringUtils.length(@newData.data) <= 4000 && getAfter(/households/$h/babies/@newData.baby) != null",
      "update": "@user.id != null && get(/households/$h/members/@user.id) != null && @newData.start <= @time.now + 3600 && (@newData.type == 'breast' || @newData.type == 'bottle' || @newData.type == 'pump' || @newData.type == 'solids' || @newData.type == 'sleep' || @newData.type == 'diaper' || @newData.type == 'growth' || @newData.type == 'medicine' || @newData.type == 'temp' || @newData.type == 'note') && @StringUtils.length(@newData.data) <= 4000 && get(/households/$h/babies/@newData.baby) != null",
      "delete": "false"
    }
  },
  "users/$uid/households/$h": {
    "description": "The families a person belongs to, so a signed-in account finds them on every device. Only the person can see or change their own list.",
    "fields": {
      "name": "String",
      "at": "UInt"
    },
    "rules": {
      "read": "@user.id != null && @user.id == $uid",
      "create": "@user.id != null && @user.id == $uid && @StringUtils.length(@newData.name) <= 60 && getAfter(/households/$h/members/$uid) != null",
      "update": "@user.id != null && @user.id == $uid && @StringUtils.length(@newData.name) <= 60",
      "delete": "@user.id != null && @user.id == $uid"
    }
  },
  "functions": {
    "api": {
      "auth": "true",
      "entry": "functions/api.ts",
      "public": true,
      "methods": [
        "GET",
        "POST",
        "DELETE"
      ],
      "cors": "passthrough"
    }
  }
}

Change history

Quality pass: undo on timers, sleep by hour chart, diaper color, fixes
c84ed88213 · 6 hours ago
Bounded source sync
1ed137d95d · 17 hours ago
Bounded source sync
851999f793 · 18 hours ago
Bounded source sync
9623bc231c · 18 hours ago